Index: /main/waeup.sirp/trunk/src/waeup/sirp/applicants/__init__.py
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/applicants/__init__.py	(revision 7239)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/applicants/__init__.py	(revision 7240)
@@ -3,6 +3,6 @@
 # Make this a package.
 from waeup.sirp.applicants.applicant import (
-    ResultEntry, Applicant, ApplicantFactory, ApplicantImageStoreHandler,
-    get_regno_or_ac, ApplicantImageNameChooser,
+    Applicant, ApplicantFactory, ApplicantImageStoreHandler,
+    ApplicantImageNameChooser,
     )
 from waeup.sirp.applicants.container import ApplicantsContainer
@@ -14,5 +14,4 @@
 
 __all__ = [
-    'ResultEntry',
     'Applicant',
     'ApplicantFactory',
Index: /main/waeup.sirp/trunk/src/waeup/sirp/applicants/applicant.py
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/applicants/applicant.py	(revision 7239)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/applicants/applicant.py	(revision 7240)
@@ -17,7 +17,9 @@
 ##
 import os
+from random import SystemRandom as r
 import grok
 from grok import index
 from zope.component.interfaces import IFactory
+from zope.securitypolicy.interfaces import IPrincipalRoleManager
 from zope.interface import implementedBy
 from hurry.workflow.interfaces import IWorkflowInfo, IWorkflowState
@@ -27,22 +29,19 @@
 from waeup.sirp.interfaces import (
     IObjectHistory, IFileStoreHandler, IFileStoreNameChooser)
-from waeup.sirp.utils.helpers import attrs_to_fields
+from waeup.sirp.utils.helpers import attrs_to_fields, get_current_principal
 from waeup.sirp.applicants.interfaces import (
-    IResultEntry, IApplicant, IApplicantEdit,
+    IApplicant, IApplicantEdit,
     )
-
-
-def get_regno_or_ac(context):
-    reg_no = getattr(context, 'reg_no', None)
-    if reg_no is None:
-        return getattr(context, 'access_code', None)
-    return reg_no
-
-class ResultEntry(grok.Context):
-    grok.implements(IResultEntry)
-
-    def __init__(self, subject=None, score=None):
-        self.subject = subject
-        self.score = score
+from waeup.sirp.applicants.workflow import INITIALIZED
+
+def generate_applicant_id(container=None):
+    if container is not None:
+        aid = u"%s_%d" % (container.code, r().randint(99999,1000000))
+        while aid in container.keys():
+            aid = u"%s_%d" % (container.code, r().randint(99999,1000000))
+        return aid
+    else:
+        # In some tests we don't use containers
+        return u"xxx_1234"
 
 class Applicant(grok.Model):
@@ -50,6 +49,8 @@
     grok.provides(IApplicant)
 
-    def __init__(self):
+    def __init__(self, container=None):
         super(Applicant, self).__init__()
+        self.applicant_id = generate_applicant_id(container)
+        self.password = None
         IWorkflowInfo(self).fireTransition('init')
         self.application_date = None
@@ -69,4 +70,18 @@
         history = IObjectHistory(self)
         return history
+
+    @property
+    def application_number(self):
+        return self.applicant_id.split('_')[1]
+
+    @property
+    def fullname(self):
+        # We do not necessarily have the middlenames attribute
+        middlenames = getattr(self, 'middlenames', None)
+        if middlenames:
+            return '%s %s %s' % (self.firstname,
+                middlenames, self.lastname)
+        else:
+            return '%s %s' % (self.firstname, self.lastname)
 
 # Set all attributes of Applicant required in IApplicant as field
@@ -84,4 +99,6 @@
 
     access_code = index.Field(attribute='access_code')
+    applicant_id = index.Field(attribute='applicant_id')
+    reg_no = index.Field(attribute='reg_no')
 
 class ApplicantFactory(grok.GlobalUtility):
@@ -94,5 +111,5 @@
 
     def __call__(self, *args, **kw):
-        return Applicant()
+        return Applicant(kw['container'])
 
     def getInterfaces(self):
@@ -147,14 +164,14 @@
         *Example:*
 
-        For an applicant with registration no. ``'My_reg_no_1234'``
+        For an applicant with applicant_id. ``'app2001_1234'``
         and stored in an applicants container called
         ``'mycontainer'``, this chooser would create:
 
-          ``'__img-applicant__mycontainer/My_reg_no_1234.jpg'``
+          ``'__img-applicant__mycontainer/app2001_1234.jpg'``
 
         meaning that the passport image of this applicant would be
         stored in the site-wide file storage in path:
 
-          ``mycontainer/My_reg_no_1234.jpg``
+          ``mycontainer/app2001_1234.jpg``
 
         If the context applicant has no parent, ``'_default'`` is used
@@ -171,5 +188,5 @@
         marked_filename = '__%s__%s/%s.jpg' % (
             APPLICANT_IMAGE_STORE_NAME,
-            parent_name, get_regno_or_ac(self.context))
+            parent_name, self.context.applicant_id)
         return marked_filename
 
@@ -214,2 +231,40 @@
         return file, path, WAeUPImageFile(
             file_obj.filename, file_obj.data)
+
+@grok.subscribe(IApplicant, grok.IObjectAddedEvent)
+def handle_applicant_added(applicant, event):
+    """If an applicant is added local and site roles are assigned.
+    """
+    role_manager = IPrincipalRoleManager(applicant)
+    role_manager.assignRoleToPrincipal(
+        'waeup.local.ApplicationOwner', applicant.applicant_id)
+    # Assign current principal the global Applicant role
+    role_manager = IPrincipalRoleManager(grok.getSite())
+    role_manager.assignRoleToPrincipal(
+        'waeup.Applicant', applicant.applicant_id)
+
+    # Assign global applicant role for new applicant (alternative way)
+    #account = IUserAccount(applicant)
+    #account.roles = ['waeup.Applicant']
+
+    return
+
+@grok.subscribe(IApplicant, grok.IObjectRemovedEvent)
+def handle_applicant_removed(applicant, event):
+    """If an applicant is removed a message is logged.
+    """
+    comment = 'Applicant record removed'
+    target = applicant.applicant_id
+    # In some tests we don't have a principal
+    try:
+        user = get_current_principal().id
+    except (TypeError, AttributeError):
+        return
+    try:
+        grok.getSite()['applicants'].logger.info('%s - %s - %s' % (
+            user, target, comment))
+    except KeyError:
+        # If we delete an entire university instance there won't be
+        # an applicants subcontainer
+        return
+    return
Index: /main/waeup.sirp/trunk/src/waeup/sirp/applicants/authentication.py
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/applicants/authentication.py	(revision 7239)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/applicants/authentication.py	(revision 7240)
@@ -1,4 +1,4 @@
 ## $Id$
-##
+## 
 ## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
 ## This program is free software; you can redistribute it and/or modify
@@ -6,249 +6,91 @@
 ## the Free Software Foundation; either version 2 of the License, or
 ## (at your option) any later version.
-##
+## 
 ## This program is distributed in the hope that it will be useful,
 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 ## GNU General Public License for more details.
-##
+## 
 ## You should have received a copy of the GNU General Public License
 ## along with this program; if not, write to the Free Software
 ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ##
-"""Special authentication for applicants.
-
-   XXX: This is work in progress, experimental code! Don't do that at home!
+"""
+Authenticate applicants.
 """
 import grok
-from zope.event import notify
-from zope.pluggableauth.factories import Principal
+from zope.component import getUtility
+from zope.password.interfaces import IPasswordManager
 from zope.pluggableauth.interfaces import (
-    ICredentialsPlugin, IAuthenticatorPlugin,
-    IAuthenticatedPrincipalFactory, AuthenticatedPrincipalCreated)
-from zope.pluggableauth.plugins.session import SessionCredentialsPlugin
-from zope.publisher.interfaces import IRequest
+    IAuthenticatorPlugin, ICredentialsPlugin)
+from zope.pluggableauth.plugins.session import (
+    SessionCredentialsPlugin, SessionCredentials)
 from zope.publisher.interfaces.http import IHTTPRequest
 from zope.session.interfaces import ISession
-from waeup.sirp.accesscodes import get_access_code
-from waeup.sirp.applicants.interfaces import (
-    IApplicantPrincipalInfo, IApplicantPrincipal, IApplicantSessionCredentials,
-    )
-from waeup.sirp.applicants import get_applicant_data
-from waeup.sirp.interfaces import IAuthPluginUtility
+from waeup.sirp.authentication import SIRPPrincipalInfo, get_principal_role_manager
+from waeup.sirp.interfaces import (
+    IAuthPluginUtility, IUserAccount, IPasswordValidator)
+from waeup.sirp.applicants.interfaces import IApplicant
+from waeup.sirp.students.authentication import (
+    StudentAccount, StudentsAuthenticatorPlugin)
 
+class ApplicantAccount(StudentAccount):
+    """An adapter to turn applicant objects into accounts on-the-fly.
+    """
+    grok.context(IApplicant)
+    grok.implements(IUserAccount)
 
-class ApplicantPrincipalInfo(object):
-    """Infos about an applicant principal.
-    """
-    grok.implements(IApplicantPrincipalInfo)
+    @property
+    def name(self):
+        return self.context.applicant_id
 
-    def __init__(self, access_code):
-        self.id = principal_id(access_code)
-        self.title = u'Applicant'
-        self.description = u'An Applicant'
-        self.credentialsPlugin = None
-        self.authenticatorPlugin = None
-        self.access_code = access_code
+    @property
+    def title(self):
+        return self.context.fullname
 
-class ApplicantPrincipal(Principal):
-    """An applicant principal.
+    @property
+    def user_type(self):
+        return u'applicant'
 
-    Applicant principals provide an extra `access_code` and `reg_no`
-    attribute extending ordinary principals.
-    """
-
-    grok.implements(IApplicantPrincipal)
-
-    def __init__(self, access_code, prefix=None):
-        self.id = principal_id(access_code)
-        if prefix is not None:
-            self.id = '%s.%s' % (prefix, self.id)
-        self.title = u'Applicant'
-        self.description = u'An applicant'
-        self.groups = []
-        self.access_code = access_code
-
-    def __repr__(self):
-        return 'ApplicantPrincipal(%r)' % self.id
-
-class AuthenticatedApplicantPrincipalFactory(grok.MultiAdapter):
-    """Creates 'authenticated' applicant principals.
-
-    Adapts (principal info, request) to an ApplicantPrincipal instance.
-
-    This adapter is used by the standard PAU to transform
-    PrincipalInfos into Principal instances.
-    """
-    grok.adapts(IApplicantPrincipalInfo, IRequest)
-    grok.implements(IAuthenticatedPrincipalFactory)
-
-    def __init__(self, info, request):
-        self.info = info
-        self.request = request
-
-    def __call__(self, authentication):
-        principal = ApplicantPrincipal(
-            self.info.access_code,
-            authentication.prefix,
-            )
-        notify(
-            AuthenticatedPrincipalCreated(
-                authentication, principal, self.info, self.request))
-        return principal
-
-
-#
-# Credentials plugins and related....
-#
-
-class ApplicantCredentials(object):
-    """Credentials class for ordinary applicants.
-    """
-    grok.implements(IApplicantSessionCredentials)
-
-    def __init__(self, access_code):
-        self.access_code = access_code
-
-    def getAccessCode(self):
-        """Get the access code.
-        """
-        return self.access_code
-
-    def getLogin(self):
-        """Stay compatible with non-applicant authenticators.
-        """
-        return None
-
-    def getPassword(self):
-        """Stay compatible with non-applicant authenticators.
-        """
-        return None
-
-class WAeUPApplicantCredentialsPlugin(grok.GlobalUtility,
-                                      SessionCredentialsPlugin):
-    """A credentials plugin that scans requests for applicant credentials.
-    """
-    grok.provides(ICredentialsPlugin)
-    grok.name('applicant_credentials')
-
-    loginpagename = 'login'
-    accesscode_prefix_field = 'form.ac_prefix'
-    accesscode_series_field = 'form.ac_series'
-    accesscode_number_field = 'form.ac_number'
-
-    def extractCredentials(self, request):
-        """Extracts credentials from a session if they exist.
-        """
-        if not IHTTPRequest.providedBy(request):
-            return None
-        session = ISession(request)
-        sessionData = session.get(
-            'zope.pluggableauth.browserplugins')
-        access_code_prefix = request.get(self.accesscode_prefix_field, None)
-        access_code_series = request.get(self.accesscode_series_field, None)
-        access_code_no = request.get(self.accesscode_number_field, None)
-        access_code = '%s-%s-%s' % (
-            access_code_prefix, access_code_series, access_code_no)
-        if None in [access_code_prefix, access_code_series, access_code_no]:
-            access_code = None
-        credentials = None
-
-        if access_code:
-            credentials = ApplicantCredentials(access_code)
-        elif not sessionData:
-            return None
-        sessionData = session[
-            'zope.pluggableauth.browserplugins']
-        if credentials:
-            sessionData['credentials'] = credentials
-        else:
-            credentials = sessionData.get('credentials', None)
-        if not credentials:
-            return None
-        if not IApplicantSessionCredentials.providedBy(credentials):
-            # If credentials were stored in session from another
-            # credentials plugin then we cannot make assumptions about
-            # its structure.
-            return None
-        return {'accesscode': credentials.getAccessCode()}
-
-
-
-class ApplicantsAuthenticatorPlugin(grok.GlobalUtility):
-    """Authenticate applicants.
-    """
+class ApplicantsAuthenticatorPlugin(StudentsAuthenticatorPlugin):
+    grok.implements(IAuthenticatorPlugin)
     grok.provides(IAuthenticatorPlugin)
     grok.name('applicants')
 
-    def authenticateCredentials(self, credentials):
-        """Validate the given `credentials`
+    def getAccount(self, login):
+        """Look up a applicant identified by `login`. Returns an account.
 
-        Credentials for applicants have to be passed as a regular
-        dictionary with a key ``accesscode``. This access code is the
-        password and username of an applicant.
+        First we split the login name into the container part and
+        the application number part. Then we simply look up the key under which
+        the applicant is stored in the respective applicants cointainer of
+        the portal.
 
-        Returns a :class:`ApplicantPrincipalInfo` in case of
-        successful validation, ``None`` else.
+        Returns not an applicant but an account object adapted from any
+        applicant found.
 
-        Credentials are not valid if:
-
-        - The passed accesscode does not exist (i.e. was not generated
-          by the :mod:`waeup.sirp.accesscode` module).
-
-        or
-
-        - the accesscode was disabled
-
-        or
-
-        - the accesscode was already used and a dataset for this
-          applicant was already generated with a different accesscode
-          (currently impossible, as applicant datasets are indexed by
-          accesscode)
-
-        or
-
-        - a dataset for the applicant already exists with an
-          accesscode set and this accesscode does not match the given
-          one.
-
+        If no such applicant exists, ``None`` is returned.
         """
-        if not isinstance(credentials, dict):
+        site = grok.getSite()
+        if site is None:
             return None
-        accesscode = credentials.get('accesscode', None)
-        if accesscode is None:
+        applicantsroot = site.get('applicants', None)
+        if applicantsroot is None:
             return None
-        applicant_data = get_applicant_data(accesscode)
-        ac = get_access_code(accesscode) # Get the real access code object
-        appl_ac = getattr(applicant_data, 'access_code', None)
-        if ac is None:
+        try:
+            container, application_number = login.split('_')
+        except ValueError:
             return None
-        if ac.state == 'disabled':
+        applicantscontainer = applicantsroot.get(container,None)
+        if applicantscontainer is None:
             return None
-        if ac.state == 'used' and appl_ac != ac.representation:
+        applicant = applicantscontainer.get(application_number, None)
+        if applicant is None:
             return None
-        # If the following fails we have a catalog error. Bad enough
-        # to pull emergency break.
-        assert appl_ac is None or appl_ac == ac.representation
-        return ApplicantPrincipalInfo(accesscode)
-
-    def principalInfo(self, id):
-        """Returns an IPrincipalInfo object for the specified principal id.
-
-        This method is used by the stadard PAU to lookup for instance
-        groups. If a principal belongs to a group, the group is looked
-        up by the id.  Currently we always return ``None``,
-        indicating, that the principal could not be found. This also
-        means, that is has no effect if applicant users belong to a
-        certain group. They can not gain extra-permissions this way.
-        """
-        return None
+        return IUserAccount(applicant)
 
 class ApplicantsAuthenticatorSetup(grok.GlobalUtility):
-    """A global utility that sets up any PAU passed.
+    """Register or unregister applicant authentication for a PAU.
 
-    The methods of this utility are called during setup of a new site
-    (`University`) instance and after the regular authentication
-    systems (regular users, officers, etc.) were set up.
+    This piece is called when a new site is created.
     """
     grok.implements(IAuthPluginUtility)
@@ -256,45 +98,12 @@
 
     def register(self, pau):
-        """Register our local applicants specific PAU components.
-
-        Applicants provide their own authentication system resulting
-        in a specialized credentials plugin and a specialized
-        authenticator plugin.
-
-        Here we tell a given PAU that these plugins exist and should
-        be consulted when trying to authenticate a user.
-
-        We stack our local plugins at end of the plugin list, so that
-        other authentication mechanisms (the normal user
-        authentication for instance) have precedence and to avoid
-        "account-shadowing".
-        """
-        # The local credentials plugin is registered under the name
-        # 'applicant_credentials' (see above).
-        plugins = list(pau.credentialsPlugins) + ['applicant_credentials']
-        pau.credentialsPlugins = tuple(plugins)
-        # The local authenticator plugin is registered under the name
-        # 'applicants' (subject to change?)
-        plugins = list(pau.authenticatorPlugins) + ['applicants']
+        plugins = list(pau.authenticatorPlugins)
+        plugins.append('applicants')
         pau.authenticatorPlugins = tuple(plugins)
         return pau
 
     def unregister(self, pau):
-        """Unregister applicant specific authentication components from PAU.
-        """
-        pau.credentialsPlugins = tuple(
-            [x for x in list(pau.credentialsPlugins)
-             if x != 'applicant_credentials'])
-        pau.authenticatorPlugins = tuple(
-            [x for x in list(pau.authenticatorPlugins)
-             if x != 'applicants'])
+        plugins = [x for x in pau.authenticatorPlugins
+                   if x != 'applicants']
+        pau.authenticatorPlugins = tuple(plugins)
         return pau
-
-
-def principal_id(access_code):
-    """Get a principal ID for applicants.
-
-    We need unique principal ids for appliants. As access codes must
-    be unique we simply return them.
-    """
-    return access_code
Index: /main/waeup.sirp/trunk/src/waeup/sirp/applicants/browser.py
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/applicants/browser.py	(revision 7239)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/applicants/browser.py	(revision 7240)
@@ -24,5 +24,5 @@
 from datetime import datetime, date
 from zope.authentication.interfaces import ILogout, IAuthentication
-from zope.component import getUtility
+from zope.component import getUtility, createObject
 from zope.formlib.widget import CustomWidgetFactory
 from zope.formlib.form import setUpEditWidgets
@@ -51,5 +51,6 @@
     )
 from waeup.sirp.interfaces import (
-    IWAeUPObject, ILocalRolesAssignable, IExtFileStore, IFileStoreNameChooser)
+    IWAeUPObject, ILocalRolesAssignable, IExtFileStore,
+    IFileStoreNameChooser, IPasswordValidator, IUserAccount)
 from waeup.sirp.permissions import get_users_with_local_roles
 from waeup.sirp.browser import DEFAULT_PASSPORT_IMAGE_PATH
@@ -61,17 +62,14 @@
 from waeup.sirp.widgets.objectwidget import (
     WAeUPObjectWidget, WAeUPObjectDisplayWidget)
-from waeup.sirp.applicants import ResultEntry, Applicant, get_applicant_data
+from waeup.sirp.applicants import Applicant, get_applicant_data
 from waeup.sirp.applicants.interfaces import (
-    IApplicant, IApplicantPrincipal,IApplicantEdit, IApplicantsRoot,
+    IApplicant, IApplicantEdit, IApplicantsRoot,
     IApplicantsContainer, IApplicantsContainerAdd, application_types_vocab,
     MAX_UPLOAD_SIZE,
     )
 from waeup.sirp.applicants.workflow import INITIALIZED, STARTED
-
-results_widget = CustomWidgetFactory(
-    WAeUPObjectWidget, ResultEntry)
-
-results_display_widget = CustomWidgetFactory(
-    WAeUPObjectDisplayWidget, ResultEntry)
+from waeup.sirp.students.viewlets import PrimaryStudentNavTab
+
+grok.context(IWAeUPObject) # Make IWAeUPObject the default context
 
 class ApplicantsRootPage(WAeUPPage):
@@ -228,5 +226,5 @@
         """Get a title for a context.
         """
-        return self.context.access_code
+        return self.context.application_number
 
 class ApplicantsAuthTab(PrimaryNavTab):
@@ -235,5 +233,5 @@
     grok.context(IWAeUPObject)
     grok.order(3)
-    grok.require('waeup.viewApplication')
+    grok.require('waeup.viewApplicationsTab')
     grok.template('primarynavtab')
     pnav = 3
@@ -263,4 +261,22 @@
     #    return tt
 
+class MyApplicationDataTab(PrimaryStudentNavTab):
+    """MyData-tab in primary navigation.
+    """
+    grok.order(3)
+    grok.require('waeup.viewMyApplicationDataTab')
+    grok.template('primarynavtab')
+    pnav = 3
+    tab_title = u'My Data'
+
+    @property
+    def link_target(self):
+        try:
+            container, application_number = self.request.principal.id.split('_')
+        except ValueError:
+            return
+        rel_link = '/applicants/%s/%s' % (container, application_number)
+        return self.view.application_url() + rel_link
+
 class ApplicantsContainerPage(WAeUPDisplayFormPage):
     """The standard view for regular applicant containers.
@@ -292,12 +308,12 @@
     text = 'Manage applicants container'
 
-class ApplicantLoginActionButton(ManageActionButton):
-    grok.order(2)
-    grok.context(IApplicantsContainer)
-    grok.view(ApplicantsContainerPage)
-    grok.require('waeup.Anonymous')
-    icon = 'login.png'
-    text = 'Login for applicants'
-    target = 'login'
+#class ApplicantLoginActionButton(ManageActionButton):
+#    grok.order(2)
+#    grok.context(IApplicantsContainer)
+#    grok.view(ApplicantsContainerPage)
+#    grok.require('waeup.Anonymous')
+#    icon = 'login.png'
+#    text = 'Login for applicants'
+#    target = 'login'
 
 class ApplicantsContainerManageFormPage(WAeUPEditFormPage):
@@ -394,4 +410,5 @@
         return del_local_roles(self,3,**data)
 
+# Not used anymore
 class ApplicantLoginPage(WAeUPPage):
     grok.context(IApplicantsContainer)
@@ -421,6 +438,6 @@
             self.flash('Entered credentials are invalid.')
             return
-        if not IApplicantPrincipal.providedBy(self.request.principal):
-            # Don't care if user is already authenticated as non-applicant
+#        if not IApplicantPrincipal.providedBy(self.request.principal):
+#            # Don't care if user is already authenticated as non-applicant
             return
 
@@ -470,8 +487,11 @@
 
         # Mark application as started
-        if IWorkflowState(self.context[pin]).getState() is INITIALIZED:
-            IWorkflowInfo(self.context[pin]).fireTransition('start')
+        #if IWorkflowState(self.context[pin]).getState() is INITIALIZED:
+        #    IWorkflowInfo(self.context[pin]).fireTransition('start')
 
         self.redirect(self.url(self.context[pin], 'edit'))
+        return
+
+    def render(self):
         return
 
@@ -482,5 +502,8 @@
     grok.require('waeup.manageApplication')
     grok.name('addapplicant')
-    grok.template('applicantaddpage')
+    #grok.template('applicantaddpage')
+    form_fields = grok.AutoFields(IApplicant).select(
+        'firstname', 'middlenames', 'lastname',
+        'email', 'phone')
     title = 'Applicants'
     label = 'Add applicant'
@@ -491,67 +514,12 @@
         return "Applicants Container: %s" % self.context.title
 
-    @property
-    def ac_prefix(self):
-        return self.context.ac_prefix
-
     @grok.action('Create application record')
     def addApplicant(self, **data):
-        ac_series = self.request.form.get('form.ac_series', None)
-        ac_number = self.request.form.get('form.ac_number', None)
-        pin = '%s-%s-%s' % (self.ac_prefix,ac_series,ac_number)
-        if not invalidate_accesscode(pin, comment=u"Invalidated by system"):
-            self.flash('%s is not a valid access code.' % pin)
-            self.redirect(self.url(self.context, '@@manage')+'#tab-2')
-            return
-        else:
-            # Create applicant record
-            applicant = Applicant()
-            applicant.access_code = pin
-            self.context[pin] = applicant
-        self.redirect(self.url(self.context[pin], 'manage'))
-        return
-
-class AccessCodeViewLink(LeftSidebarLink):
-    grok.order(1)
-    grok.require('waeup.Public')
-    icon = 'actionicon_view.png'
-    title = 'View Record'
-    target = '/@@index'
-
-    @property
-    def url(self):
-        if not IApplicantPrincipal.providedBy(self.request.principal):
-            return ''
-        access_code = getattr(self.request.principal,'access_code',None)
-        if access_code:
-            applicant_object = get_applicant_data(access_code)
-            return absoluteURL(applicant_object, self.request) + self.target
-        return ''
-
-class AccessCodeEditLink(AccessCodeViewLink):
-    grok.order(2)
-    grok.require('waeup.Public')
-    icon = 'actionicon_modify.png'
-    title = 'Edit Record'
-    target = '/@@edit'
-
-    @property
-    def url(self):
-        if not IApplicantPrincipal.providedBy(self.request.principal):
-            return ''
-        access_code = getattr(self.request.principal,'access_code',None)
-        if access_code:
-            applicant_object = get_applicant_data(access_code)
-            if applicant_object.locked:
-                return ''
-            return absoluteURL(applicant_object, self.request) + self.target
-        return ''
-
-class AccessCodeSlipLink(AccessCodeViewLink):
-    grok.order(3)
-    grok.require('waeup.Public')
-    icon = 'actionicon_pdf.png'
-    title = 'Download Slip'
-    target = '/application_slip.pdf'
+        applicant = createObject(u'waeup.Applicant', container = self.context)
+        self.applyData(applicant, **data)
+        self.context.addApplicant(applicant)
+        self.flash('Applicant record created.')
+        self.redirect(self.url(self.context[applicant.application_number], 'index'))
+        return
 
 class ApplicantDisplayFormPage(WAeUPDisplayFormPage):
@@ -561,5 +529,5 @@
     grok.template('applicantdisplaypage')
     form_fields = grok.AutoFields(IApplicant).omit(
-        'locked').omit('course_admitted')
+        'locked', 'course_admitted', 'password')
     form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
     label = 'Applicant'
@@ -568,16 +536,24 @@
     def update(self):
         self.passport_url = self.url(self.context, 'passport.jpg')
-        return
+        # Mark application as started if applicant logs in for the first time
+        if IWorkflowState(self.context).getState() == INITIALIZED:
+            IWorkflowInfo(self.context).fireTransition('start')
+        return
+
+    @property
+    def hasPassword(self):
+        if self.context.password:
+            return 'set'
+        return 'unset'
 
     @property
     def title(self):
-        if self.request.principal.title == 'Applicant':
-            return u'Your Application Record'
-        return '%s' % self.context.access_code
+        return 'Application Record %s' % self.context.application_number
 
     @property
     def label(self):
         container_title = self.context.__parent__.title
-        return '%s Application Record' % container_title
+        return '%s Application Record %s' % (
+            container_title, self.context.application_number)
 
     def getCourseAdmitted(self):
@@ -607,5 +583,5 @@
     grok.require('waeup.viewApplication')
     form_fields = grok.AutoFields(IApplicant).omit(
-        'locked').omit('course_admitted')
+        'locked', 'course_admitted')
     form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
     prefix = 'form'
@@ -614,5 +590,6 @@
     def label(self):
         container_title = self.context.__parent__.title
-        return '%s Application Record' % container_title
+        return '%s Application Record %s' % (
+            container_title, self.context.application_number)
 
     def getCourseAdmitted(self):
@@ -710,4 +687,18 @@
     target = 'manage'
 
+class ApplicantEditActionButton(ManageActionButton):
+    grok.context(IApplicant)
+    grok.view(ApplicantDisplayFormPage)
+    grok.require('waeup.handleApplication')
+    text = 'Edit application record'
+    target ='edit'
+
+    @property
+    def target_url(self):
+        """Get a URL to the target...
+        """
+        if self.context.locked:
+            return
+        return self.view.url(self.view.context, self.target)
 
 def handle_img_upload(upload, context, view):
@@ -756,10 +747,11 @@
     @property
     def title(self):
-        return self.context.access_code
+        return 'Application Record %s' % self.context.application_number
 
     @property
     def label(self):
         container_title = self.context.__parent__.title
-        return '%s Application Form' % container_title
+        return '%s Application Form %s' % (
+            container_title, self.context.application_number)
 
     def getTransitions(self):
@@ -776,4 +768,13 @@
     @grok.action('Save')
     def save(self, **data):
+        form = self.request.form
+        password = form.get('password', None)
+        password_ctl = form.get('control_password', None)
+        if password:
+            validator = getUtility(IPasswordValidator)
+            errors = validator.validate_password(password, password_ctl)
+            if errors:
+                self.flash( ' '.join(errors))
+                return
         if self.passport_changed is False:  # False is not None!
             return # error during image upload. Ignore other values
@@ -782,9 +783,13 @@
         if changed_fields:
             changed_fields = reduce(lambda x,y: x+y, changed_fields.values())
+        else:
+            changed_fields = []
+        if self.passport_changed:
+            changed_fields.append('passport')
+        if password:
+            # Now we know that the form has no errors and can set password ...
+            IUserAccount(self.context).setPassword(password)
+            changed_fields.append('password')
         fields_string = ' + '.join(changed_fields)
-        if self.passport_changed:
-            fields_string += ' + passport'
-        #self.context._p_changed = True
-        form = self.request.form
         trans_id = form.get('transition', None)
         if trans_id:
@@ -804,5 +809,5 @@
     form_fields = grok.AutoFields(IApplicantEdit).omit(
         'locked', 'course_admitted', 'student_id',
-        'screening_score',
+        'screening_score', 'applicant_id'
         )
     form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
@@ -863,5 +868,5 @@
     grok.context(IApplicant)
     grok.view(ApplicantManageFormPage)
-    grok.require('waeup.manageApplication')
+    grok.require('waeup.viewApplication')
     icon = 'actionicon_view.png'
     text = 'View application record'
Index: in/waeup.sirp/trunk/src/waeup/sirp/applicants/browser_templates/applicantaddpage.pt
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/applicants/browser_templates/applicantaddpage.pt	(revision 7239)
+++ 	(revision )
@@ -1,29 +1,0 @@
-<h2 i18n:translate=""
-    tal:condition="view/label"
-    tal:content="view/label">Label</h2>
-
-<form method="POST">
-  <table>
-    <tbody>
-      <tr>
-    	<td><label for="form.ac_series">Access Code:</label></td>
-    	<td>
-          <span tal:replace="view/ac_prefix">PIN Prefix</span>
-         -<input name="form.ac_series" type="text" size="3" maxlength="3"
-                  value="" />
-         -<input name="form.ac_number" type="text" size="10" maxlength="10"
-                value="" />
-    	</td>
-      </tr>
-      <tr>
-        <td>
-            <div class="actionButtons" tal:condition="view/availableActions">
-                <span tal:repeat="action view/actions" tal:omit-tag="">
-                    <input tal:replace="structure action/render"/>
-                </span>
-            </div>
-        </td>
-      </tr>
-    </tbody>
-  </table>
-</form>
Index: /main/waeup.sirp/trunk/src/waeup/sirp/applicants/browser_templates/applicantdisplaypage.pt
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/applicants/browser_templates/applicantdisplaypage.pt	(revision 7239)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/applicants/browser_templates/applicantdisplaypage.pt	(revision 7240)
@@ -11,9 +11,8 @@
 </div>
 
+<img src="" tal:attributes="src view/passport_url" />
+
 <table class="zebra">
   <tbody>
-    <tr>
-    <td><img src="" tal:attributes="src view/passport_url" /></td>
-    </tr>
     <tal:block repeat="widget view/widgets">
       <tr>
@@ -34,4 +33,12 @@
       </td>
     </tr>
+    <tr>
+      <td class="fieldname">
+          Password:
+      </td>
+      <td>
+          <tal:password replace="view/hasPassword" />
+      </td>
+    <tr>
   </tbody>
 </table>
Index: /main/waeup.sirp/trunk/src/waeup/sirp/applicants/browser_templates/applicanteditpage.pt
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/applicants/browser_templates/applicanteditpage.pt	(revision 7239)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/applicants/browser_templates/applicanteditpage.pt	(revision 7240)
@@ -23,5 +23,5 @@
   </div>
 
-  <table class="form-fields zebra">
+  <table class="zebra">
     <tbody>
       <tal:block repeat="widget view/widgets">
@@ -62,4 +62,16 @@
       </tr>
       <tr tal:condition="view/manage_applications">
+        <td class="label"><label>Password:</label></td>
+        <td>
+          <input name="password" type="password"  />
+        </td>
+      </tr>
+      <tr tal:condition="view/manage_applications">
+        <td class="label"><label>Retype password:</label></td>
+        <td>
+          <input name="control_password" type="password" />
+        </td>
+      </tr>
+      <tr tal:condition="view/manage_applications">
         <td class="label"><label>Application Transition:</label></td>
         <td>
Index: in/waeup.sirp/trunk/src/waeup/sirp/applicants/browser_templates/applicantloginpage.pt
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/applicants/browser_templates/applicantloginpage.pt	(revision 7239)
+++ 	(revision )
@@ -1,64 +1,0 @@
-<h2 i18n:translate=""
-    tal:condition="view/label"
-    tal:content="view/label">Label</h2>
-
-
-<form method="POST">
-  <input type="hidden" name="form.ac_prefix"
-         value="" tal:attributes="value view/ac_prefix" />
-  <table>
-    <tbody>
-      <tr>
-    	<td><label for="form.ac_series">Access Code:</label></td>
-    	<td>
-          <span tal:replace="view/ac_prefix">PIN Prefix</span>
-         -<input name="form.ac_series" type="text" size="3" maxlength="3"
-                  value="" tal:attributes="value view/ac_series"/>
-         -<input name="form.ac_number" type="text" size="10" maxlength="10"
-                value="" tal:attributes="value view/ac_number" />
-    	</td>
-      </tr>
-      <tr>
-	<td colspan="2">
-	  <input type="submit" name="SUBMIT" value="Show Application Record" />
-	</td>
-      </tr>
-    </tbody>
-  </table>
-</form>
-
-<div>
-  <h3>Instructions:</h3>
-  <ul class="list">
-    <li>
-      Enter your Application PIN.
-    </li>
-    <li>
-      Fill the form and upload your Passport Photograph with red
-      background in jpg format.
-    </li>
-    <li>
-      Save and preview picture.
-    </li>
-    <li>
-      Press the 'Final Submit' button.
-    </li>
-    <li>
-      After submission you won't be able to edit the data
-      but you can access and print this form again.
-    </li>
-  </ul>
-</div>
-<br />
-<div tal:condition="context/startdate">
-  Start date of application:
-  <span tal:replace="python: context.startdate.strftime('%d/%m/%Y') ">STARTDATE</span>
-</div>
-<div tal:condition="context/enddate">
-  Closing date of application:
-  <span tal:replace="python: context.enddate.strftime('%d/%m/%Y')">ENDDATE</span>
-</div>
-<br />
-<div tal:condition="context/strict_deadline">
-  No application after deadline!
-</div>
Index: /main/waeup.sirp/trunk/src/waeup/sirp/applicants/container.py
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/applicants/container.py	(revision 7239)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/applicants/container.py	(revision 7240)
@@ -24,5 +24,5 @@
 from waeup.sirp.applicants.interfaces import (
     IApplicantsContainer, IApplicantsContainerAdd,
-    IApplicantsContainerProvider,
+    IApplicantsContainerProvider, IApplicant
     )
 from waeup.sirp.utils.helpers import attrs_to_fields
@@ -83,4 +83,13 @@
         raise NotImplementedError()
 
+    def addApplicant(self, applicant):
+        """Add an applicant.
+        """
+        if not IApplicant.providedBy(applicant):
+            raise TypeError(
+                'ApplicantsContainers contain only IApplicant instances')
+        self[applicant.application_number] = applicant
+        return
+
 ApplicantsContainer = attrs_to_fields(ApplicantsContainer)
 
Index: /main/waeup.sirp/trunk/src/waeup/sirp/applicants/interfaces.py
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/applicants/interfaces.py	(revision 7239)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/applicants/interfaces.py	(revision 7240)
@@ -29,5 +29,5 @@
 from zope.security.interfaces import IGroupClosureAwarePrincipal as IPrincipal
 from zc.sourcefactory.basic import BasicSourceFactory
-from waeup.sirp.interfaces import IWAeUPObject, year_range
+from waeup.sirp.interfaces import IWAeUPObject, year_range, validate_email
 from waeup.sirp.university.vocabularies import application_categories
 from waeup.sirp.students.vocabularies import (
@@ -42,15 +42,4 @@
 MAX_UPLOAD_SIZE = 1024 * 20
 
-# Define a valiation method for email addresses
-class NotAnEmailAddress(schema.ValidationError):
-    __doc__ = u"Invalid email address"
-
-check_email = re.compile(
-    r"[a-zA-Z0-9._%-]+@([a-zA-Z0-9-]+.)*[a-zA-Z]{2,4}").match
-def validate_email(value):
-    if not check_email(value):
-        raise NotAnEmailAddress(value)
-    return True
-
 class ApplicantContainerProviderSource(BasicSourceFactory):
     """A source offering all available applicants container types.
@@ -90,16 +79,4 @@
         return "%s - %s" % (
             factory.container_title, factory.container_description)
-
-class IResultEntry(IWAeUPObject):
-    subject = schema.TextLine(
-        title = u'Subject',
-        description = u'The subject',
-        required=False,
-        )
-    score = schema.TextLine(
-        title = u'Score',
-        description = u'The score',
-        required=False,
-        )
 
 class IApplicantsRoot(IWAeUPObject, IContainer):
@@ -263,5 +240,5 @@
     """The data for an applicant.
 
-    This is a base interface with no field (except ``reg_no``)
+    This is a base interface with no field
     required. For use with importers, forms, etc., please use one of
     the derived interfaces below, which set more fields to required
@@ -269,10 +246,9 @@
     """
     history = Attribute('Object history, a list of messages.')
-    state = Attribute('Returns the application state of an applicant')
+    state = Attribute('The application state of an applicant')
+    fullname = Attribute('The fullname of an applicant')
     application_date = Attribute('Date of submission, used for export only')
-
-    #def getApplicantsRootLogger():
-    #    """Returns the logger from the applicants root object
-    #    """
+    password = Attribute('Encrypted password of a applicant')
+    application_number = Attribute('The key under which the record is stored')
 
     def loggerInfo(ob_class, comment):
@@ -280,7 +256,14 @@
         """
 
+    applicant_id = schema.TextLine(
+        title = u'Applicant Id',
+        required = False,
+        readonly = True,
+        )
+
     reg_no = schema.TextLine(
         title = u'JAMB Registration Number',
         readonly = True,
+        required = False,
         )
     access_code = schema.TextLine(
@@ -329,5 +312,5 @@
     email = schema.ASCIILine(
         title = u'Email',
-        required = False,
+        required = True,
         constraint=validate_email,
         )
@@ -416,52 +399,4 @@
         )
 
-class IApplicantPrincipalInfo(IPrincipalInfo):
-    """Infos about principals that are applicants.
-    """
-    access_code = Attribute("The Access Code the user purchased")
-
-class IApplicantPrincipal(IPrincipal):
-    """A principal that is an applicant.
-
-    This interface extends zope.security.interfaces.IPrincipal and
-    requires also an `id` and other attributes defined there.
-    """
-    access_code = schema.TextLine(
-        title = u'Access Code',
-        description = u'The access code purchased by the user.',
-        required = True,
-        readonly = True)
-
-class IApplicantsFormChallenger(Interface):
-    """A challenger that uses a browser form to collect applicant
-       credentials.
-    """
-    loginpagename = schema.TextLine(
-        title = u'Loginpagename',
-        description = u"""Name of the login form used by challenger.
-
-        The form must provide an ``access_code`` input field.
-        """)
-
-    accesscode_field = schema.TextLine(
-        title = u'Access code field',
-        description = u'''Field of the login page which is looked up for
-                          access_code''',
-        default = u'access_code',
-        )
-
-
-class IApplicantSessionCredentials(Interface):
-    """Interface for storing and accessing applicant credentials in a
-       session.
-    """
-
-    def __init__(access_code):
-        """Create applicant session credentials."""
-
-    def getAccessCode():
-        """Return the access code."""
-
-
 class IApplicantsContainerProvider(Interface):
     """A provider for applicants containers.
Index: /main/waeup.sirp/trunk/src/waeup/sirp/applicants/permissions.py
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/applicants/permissions.py	(revision 7239)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/applicants/permissions.py	(revision 7240)
@@ -29,4 +29,10 @@
     grok.name('waeup.viewApplication')
 
+class ViewApplicationsTab(grok.Permission):
+    grok.name('waeup.viewApplicationsTab')
+
+class ViewMyApplicationDataTab(grok.Permission):
+    grok.name('waeup.viewMyApplicationDataTab')
+
 class ManageApplication(grok.Permission):
     grok.name('waeup.manageApplication')
@@ -36,5 +42,6 @@
     grok.name('waeup.local.ApplicationOwner')
     grok.title(u'Application Owner')
-    grok.permissions('waeup.handleApplication', 'waeup.viewApplication')
+    grok.permissions('waeup.handleApplication', 'waeup.viewApplication',
+                     'waeup.Authenticated')
 
 # Site role
@@ -42,8 +49,9 @@
 class ApplicantRole(grok.Role):
     grok.name('waeup.Applicant')
-    grok.permissions('waeup.viewAcademics')
+    grok.permissions('waeup.viewAcademics', 'waeup.viewMyApplicationDataTab')
 
 class ApplicationsOfficer(grok.Role):
     grok.name('waeup.ApplicationsOfficer')
     grok.title(u'Applications Officer')
-    grok.permissions('waeup.manageApplication', 'waeup.viewApplication')
+    grok.permissions('waeup.manageApplication', 'waeup.viewApplication'
+                     'waeup.viewApplicationsTab')
Index: /main/waeup.sirp/trunk/src/waeup/sirp/applicants/tests/test_applicant.py
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/applicants/tests/test_applicant.py	(revision 7239)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/applicants/tests/test_applicant.py	(revision 7240)
@@ -32,8 +32,8 @@
 from waeup.sirp.interfaces import IFileStoreHandler, IFileStoreNameChooser
 from waeup.sirp.applicants import (
-    ResultEntry, Applicant, ApplicantFactory, get_regno_or_ac,
+    Applicant, ApplicantFactory, ApplicantsContainer,
     ApplicantImageStoreHandler, ApplicantImageNameChooser,
     )
-from waeup.sirp.applicants.interfaces import IResultEntry, IApplicant
+from waeup.sirp.applicants.interfaces import IApplicant
 from waeup.sirp.testing import FunctionalTestCase, FunctionalLayer
 
@@ -56,24 +56,4 @@
         super(HelperTests, self).tearDown()
         shutil.rmtree(self.workdir)
-        return
-
-    def test_get_regno_or_ac(self):
-        # we can get reg_no or access_code of an applicants if it is set
-        appl1 = Applicant()
-        appl2 = Applicant()
-        appl2.reg_no = u'foo'
-        appl3 = Applicant()
-        appl3.access_code = u'bar'
-        appl4 = Applicant()
-        appl4.reg_no = u'foo'
-        appl4.access_code = u'bar'
-        self.assertTrue(
-            get_regno_or_ac(appl1) is None)
-        self.assertEqual(
-            get_regno_or_ac(appl2), u'foo')
-        self.assertEqual(
-            get_regno_or_ac(appl3), u'bar')
-        self.assertEqual(
-            get_regno_or_ac(appl4), u'foo')
         return
 
@@ -124,79 +104,4 @@
         return
 
-class ApplicantImageNameChooserTests(FunctionalTestCase):
-
-    layer = FunctionalLayer
-
-    def test_iface(self):
-        # make sure we implement promised interfaces
-        obj = ApplicantImageNameChooser(None) # needs a context normally
-        verify.verifyClass(IFileStoreNameChooser, ApplicantImageNameChooser)
-        verify.verifyObject(IFileStoreNameChooser, obj)
-        return
-
-    def test_name_chooser_available(self):
-        # we can get a name chooser for applicant objects as adapter
-        appl = Applicant()
-        chooser = IFileStoreNameChooser(appl)
-        self.assertTrue(chooser is not None)
-        return
-
-    def test_name_chooser_applicant_wo_container(self):
-        # we can get an image filename for applicants not in a container
-        appl = Applicant()
-        appl.reg_no = u'MY_REG_NO'
-        chooser = IFileStoreNameChooser(appl)
-        result = chooser.chooseName()
-        # the file would be stored in a ``_default`` directory.
-        self.assertEqual(
-            result, '__img-applicant___default/MY_REG_NO.jpg')
-        return
-
-    def test_name_chooser_applicant_w_container(self):
-        appl = Applicant()
-        appl.reg_no = u'MY_REG_NO'
-        fake_container = grok.Container()
-        fake_container.__name__ = 'folder'
-        fake_container['appl'] = appl
-        appl.__parent__ = fake_container
-        chooser = IFileStoreNameChooser(appl)
-        result = chooser.chooseName()
-        self.assertEqual(
-            result, '__img-applicant__folder/MY_REG_NO.jpg')
-        return
-
-    def test_name_chooser_check_name(self):
-        # we can check file ids for applicants
-        appl = Applicant()
-        appl.reg_no = u'MY_REG_NO'
-        fake_container = grok.Container()
-        fake_container.__name__ = 'folder'
-        fake_container['appl'] = appl
-        appl.__parent__ = fake_container
-        chooser = IFileStoreNameChooser(appl)
-        result1 = chooser.checkName('foo')
-        result2 = chooser.checkName('__img-applicant__folder/MY_REG_NO.jpg')
-        self.assertEqual(result1, False)
-        self.assertEqual(result2, True)
-        return
-
-class ResultEntryTest(unittest.TestCase):
-
-    def setUp(self):
-        self.result_entry = ResultEntry()
-        return
-
-    def tearDown(self):
-        pass
-
-    def test_interfaces(self):
-        verify.verifyClass(IResultEntry, ResultEntry)
-        verify.verifyObject(IResultEntry, self.result_entry)
-
-    def test_resultentry(self):
-        entry = ResultEntry('Some subject', 3.7)
-        assert entry.subject == 'Some subject'
-        assert entry.score == 3.7
-
 class ApplicantTest(FunctionalTestCase):
 
@@ -235,5 +140,5 @@
 
     def test_factory(self):
-        obj = self.factory()
+        obj = self.factory(container=None)
         assert isinstance(obj, Applicant)
 
Index: /main/waeup.sirp/trunk/src/waeup/sirp/applicants/tests/test_authentication.py
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/applicants/tests/test_authentication.py	(revision 7239)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/applicants/tests/test_authentication.py	(revision 7240)
@@ -16,393 +16,169 @@
 ## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ##
-import shutil
-import tempfile
 import unittest
 from zope.authentication.interfaces import IAuthentication
-from zope.component import provideAdapter, getUtility, queryUtility
-from zope.component.hooks import setSite
-from zope.interface import verify
-from zope.pluggableauth.interfaces import  IAuthenticatedPrincipalFactory
-from zope.publisher.browser import TestRequest
-from zope.publisher.interfaces import IRequest
-from zope.session.interfaces import ISession
-from zope.testbrowser.testing import Browser
-from zope.testing import cleanup
-from waeup.sirp.testing import FunctionalLayer, FunctionalTestCase
-from waeup.sirp.app import University
-from waeup.sirp.interfaces import IAuthPluginUtility
-from waeup.sirp.applicants import  ApplicantsContainer, Applicant
+from zope.component import provideUtility, queryUtility, getGlobalSiteManager
+from zope.interface.verify import verifyClass, verifyObject
+from zope.password.password import SSHAPasswordManager
+from zope.password.interfaces import IPasswordManager
+from zope.pluggableauth import PluggableAuthentication
+from zope.security.interfaces import Unauthorized
+from zope.securitypolicy.role import Role
+from zope.securitypolicy.interfaces import IRole, Allow
+from waeup.sirp.authentication import get_principal_role_manager
+from waeup.sirp.interfaces import IAuthPluginUtility, IUserAccount
 from waeup.sirp.applicants.authentication import (
-    ApplicantsAuthenticatorPlugin, WAeUPApplicantCredentialsPlugin,
-    ApplicantCredentials, AuthenticatedApplicantPrincipalFactory,
-    ApplicantPrincipalInfo, ApplicantPrincipal, ApplicantsAuthenticatorSetup)
+    ApplicantsAuthenticatorSetup, ApplicantAccount)
+from waeup.sirp.applicants.tests.test_browser import ApplicantsFullSetup
+from waeup.sirp.testing import FunctionalLayer
+
+class ApplicantsAuthenticatorSetupTests(unittest.TestCase):
+
+    def test_iface(self):
+        obj = ApplicantsAuthenticatorSetup()
+        verifyClass(IAuthPluginUtility, ApplicantsAuthenticatorSetup)
+        verifyObject(IAuthPluginUtility, obj)
+        return
+
+    def test_register(self):
+        # Make sure registration works.
+        setup = ApplicantsAuthenticatorSetup()
+        pau = PluggableAuthentication()
+        setup.register(pau)
+        self.assertTrue('applicants' in pau.authenticatorPlugins)
+        return
+
+    def test_unregister(self):
+        # Make sure deregistration works.
+        setup = ApplicantsAuthenticatorSetup()
+        pau = PluggableAuthentication()
+        pau.authenticatorPlugins = ('applicants')
+        setup.unregister(pau)
+        self.assertTrue('applicants' not in pau.authenticatorPlugins)
+        return
 
 
-class FakeBatch(dict):
-    def getAccessCode(self, id):
-        return self.get(id)
+class FakeApplicant(object):
+    applicant_id = 'test_appl'
+    fullname = 'Tilman Gause'
+    password = None
+    email = None
+    phone = None
 
-class FakeAccessCode(object):
-    def __init__(self, repr, state = 'initialized'):
-        self.representation = repr
-        self.state = state
 
-class FakeApplication(object):
-    def __init__(self, ac=None):
-        self.access_code = ac
+class MinimalPAU(PluggableAuthentication):
+    def getPrincipal(self, id):
+        return 'faked principal'
 
-def FakeSite():
-    return {
-        'applicants': {
-            'APP': {
-                'APP-12345': FakeApplication(u'APP-12345'),
-                'APP-54321': FakeApplication(u'APP-54321'),
-                'APP-22222': FakeApplication(u'APP-OTHER'),
-                'APP-44444': FakeApplication(),
-                'APP-55555': FakeApplication(u'APP-OTHER'),
-                'APP-77777': FakeApplication(u'APP-77777'),
-                },
-            },
-        'accesscodes': {
-            'APP': FakeBatch({
-                    'APP-12345': FakeAccessCode('APP-12345'),
-                    'APP-54321': FakeAccessCode('APP-54321', 'used'),
-                    'APP-11111': FakeAccessCode('APP-11111'),
-                    'APP-22222': FakeAccessCode('APP-22222'),
-                    'APP-33333': FakeAccessCode('APP-33333', 'used'),
-                    'APP-44444': FakeAccessCode('APP-44444', 'used'),
-                    'APP-55555': FakeAccessCode('APP-55555', 'used'),
-                    'APP-66666': FakeAccessCode('APP-66666', 'disabled'),
-                    'APP-77777': FakeAccessCode('APP-77777', 'disabled'),
-                    })
-            }
-        }
-
-class AuthenticatorPluginTest(FunctionalTestCase):
-
-    layer = FunctionalLayer
-
-    def create_applicant(self, cname, aname, ac=None):
-        # Create an applicant in an applicants container
-        setSite(self.app)
-        if not cname in self.app['applicants'].keys():
-            container = ApplicantsContainer()
-            self.app['applicants'][cname] = container
-        applicant = Applicant()
-        if ac is not None:
-            applicant.access_code = ac
-        self.app['applicants'][cname][aname] = applicant
-        return
+class ApplicantAccountTests(unittest.TestCase):
 
     def setUp(self):
-        super(AuthenticatorPluginTest, self).setUp()
+        self.fake_stud = FakeApplicant()
+        self.account = ApplicantAccount(self.fake_stud)
 
-        # Setup a sample site for each test
-        app = University()
-        self.dc_root = tempfile.mkdtemp()
-        app['datacenter'].setStoragePath(self.dc_root)
+        # We provide a minimal PAU
+        pau = MinimalPAU()
+        provideUtility(pau, IAuthentication)
 
-        # Prepopulate the ZODB...
-        self.getRootFolder()['app'] = app
-        self.app = self.getRootFolder()['app']
+        # We register a role
+        test_role = Role('waeup.test.Role', 'Testing Role')
+        provideUtility(test_role, IRole, name='waeup.test.Role')
 
-        fake_site = FakeSite()
-        for ckey, fake_container in fake_site['applicants'].items():
-            for akey, fake_appl in fake_container.items():
-                self.create_applicant(ckey, akey, fake_appl.access_code)
-        del self.app['accesscodes']
-        self.app['accesscodes'] = fake_site['accesscodes']
-
-        self.plugin = ApplicantsAuthenticatorPlugin()
+        # We have to setup a password manager utility manually as we
+        # have no functional test. In functional tests this would
+        # happen automatically, but it would take a lot more time to
+        # run the tests.
+        provideUtility(
+            SSHAPasswordManager(), IPasswordManager, 'SSHA')
         return
 
     def tearDown(self):
-        super(AuthenticatorPluginTest, self).tearDown()
-        shutil.rmtree(self.dc_root)
+        self.account.roles = [] # make sure roles are reset
+        gsm = getGlobalSiteManager()
+        to_clean = []
+        # Clear up utilities registered in setUp
+        to_clean.append(
+            (IPasswordManager, queryUtility(
+                    IPasswordManager, name='SSHA', default=None)))
+        to_clean.append(
+            (IAuthentication, queryUtility(IAuthentication, default=None)))
+        to_clean.append(
+            (IRole, queryUtility(IRole, name='test.Role', default=None)))
+        for iface, elem in to_clean:
+            if elem is not None:
+                gsm.unregisterUtility(elem, iface)
         return
 
-    def test_invalid_credentials(self):
-        result = self.plugin.authenticateCredentials('not-a-dict')
-        assert result is None
-
-        result = self.plugin.authenticateCredentials(
-            dict(accesscode=None, foo='blah'))
-        assert result is None
-
-        result = self.plugin.authenticateCredentials(
-            dict(accesscode='Nonsense',))
-        assert result is None
-
-        # Possible cases, where formal correct authentication
-        # data is not valid:
-        result = self.plugin.authenticateCredentials(
-            dict(accesscode='APP-33333'))
-        assert result is None
-
-        result = self.plugin.authenticateCredentials(
-            dict(accesscode='APP-55555'))
-        assert result is None
-
-        result = self.plugin.authenticateCredentials(
-            dict(accesscode='APP-66666'))
-        assert result is None
-
-        result = self.plugin.authenticateCredentials(
-            dict(accesscode='APP-77777'))
-        assert result is None
-
+    def test_iface(self):
+        verifyClass(IUserAccount, ApplicantAccount)
+        verifyObject(IUserAccount, self.account)
         return
 
-    def test_valid_credentials(self):
-        """The six different cases where we allow login.
-
-        All other combinations should be forbidden.
-        """
-        result = self.plugin.authenticateCredentials(
-            dict(accesscode='APP-11111'))
-        assert result is not None
-
-        result = self.plugin.authenticateCredentials(
-            dict(accesscode='APP-12345'))
-        assert result is not None
-
-        result = self.plugin.authenticateCredentials(
-            dict(accesscode='APP-54321'))
-        assert result is not None
-
-        # check the `principalInfo` method of authenticator
-        # plugin. This is only here to satisfy the coverage report.
-        assert self.plugin.principalInfo('not-an-id') is None
+    def test_set_password(self):
+        # make sure we can set a password.
+        self.account.setPassword('secret')
+        self.assertTrue(self.fake_stud.password is not None)
+        # we do not store plaintext passwords
+        self.assertTrue(self.fake_stud.password != 'secret')
+        # passwords are stored as unicode
+        self.assertTrue(isinstance(self.fake_stud.password, unicode))
         return
 
-session_data = {
-    'zope.pluggableauth.browserplugins': {}
-    }
-
-class FakeSession(dict):
-    def __init__(self, request):
-        pass
-
-    def get(self, key, default=None):
-        return self.__getitem__(key, default)
-
-    def __getitem__(self, key, default=None):
-        return session_data.get(key, default)
-
-    def __setitem__(self, key, value):
-        session_data[key] = value
+    def test_check_password(self):
+        # make sure we can check a password.
+        self.account.setPassword('secret')
+        result1 = self.account.checkPassword(None)
+        result2 = self.account.checkPassword('nonsense')
+        result3 = self.account.checkPassword('secret')
+        self.assertEqual(result1, False)
+        self.assertEqual(result2, False)
+        self.assertEqual(result3, True)
         return
 
-class CredentialsPluginTest(unittest.TestCase):
-
-    def setUp(self):
-        self.request = TestRequest()
-        provideAdapter(FakeSession, (IRequest,), ISession)
-        self.plugin = WAeUPApplicantCredentialsPlugin()
-        self.full_request = TestRequest()
-        session_data['zope.pluggableauth.browserplugins'] = {}
+    def test_check_unset_password(self):
+        # empty and unset passwords do not match anything
+        self.fake_stud.password = None
+        result1 = self.account.checkPassword('')
+        self.fake_stud.password = ''
+        result2 = self.account.checkPassword('')
+        self.assertEqual(result1, False)
+        self.assertEqual(result2, False)
         return
 
-    def tearDown(self):
-        cleanup.tearDown()
+    def test_check_password_no_string(self):
+        # if passed in password is not a string, we gain no access
+        self.fake_stud.password = 'secret'
+        result1 = self.account.checkPassword(None)
+        result2 = self.account.checkPassword(object())
+        self.assertEqual(result1, False)
+        self.assertEqual(result2, False)
         return
 
-    def filled_request(self, form_dict):
-        request = TestRequest()
-        for key, value in form_dict.items():
-            request.form[key] = value
-        return request
-
-    def test_extractCredentials_invalid(self):
-        result = self.plugin.extractCredentials('not-a-request')
-        assert result is None
+    def test_role_set(self):
+        # make sure we can set roles for principals denoted by account
+        prm = get_principal_role_manager()
+        self.assertEqual(prm.getPrincipalsAndRoles(), [])
+        self.account.roles = ['waeup.test.Role']
+        self.assertEqual(
+            prm.getPrincipalsAndRoles(),
+            [('waeup.test.Role', 'test_appl', Allow)])
         return
 
-    def test_extractCredentials_empty(self):
-        result = self.plugin.extractCredentials(self.request)
-        assert result is None
+    def test_role_get(self):
+        # make sure we can get roles set for an account
+        self.assertEqual(self.account.roles, [])
+        self.account.roles = ['waeup.test.Role',] # set a role
+        self.assertEqual(self.account.roles, ['waeup.test.Role'])
         return
 
-    def test_extractCredentials_full_set(self):
-        request = self.filled_request({
-                'form.ac_prefix': 'APP',
-                'form.ac_series': '1',
-                'form.ac_number': '1234567890',
-                #'form.jamb_reg_no': 'JAMB_NUMBER',
-                })
-        result = self.plugin.extractCredentials(request)
-        self.assertEqual(result, {'accesscode': 'APP-1-1234567890'})
-        return
-
-    def test_extractCredentials_accesscode_only(self):
-        request = self.filled_request({
-                'form.ac_prefix': 'APP',
-                'form.ac_series': '1',
-                'form.ac_number': '1234567890',
-                })
-        result = self.plugin.extractCredentials(request)
-        self.assertEqual(result, {'accesscode': 'APP-1-1234567890'})
-        return
-
-    def test_extractCredentials_from_empty_session(self):
-        session_data['zope.pluggableauth.browserplugins']['credentials'] = None
-        result = self.plugin.extractCredentials(self.request)
-        assert result is None
-        return
-
-    def test_extractCredentials_from_nonempty_session(self):
-        credentials = ApplicantCredentials('APP-1-12345')
-        session_data['zope.pluggableauth.browserplugins'][
-            'credentials'] = credentials
-        result = self.plugin.extractCredentials(self.request)
-        self.assertEqual(result, {'accesscode': 'APP-1-12345'})
-        return
-
-
-class ApplicantCredentialsTest(unittest.TestCase):
-
-    def setUp(self):
-        self.credentials = ApplicantCredentials('SOME_ACCESSCODE')
-        return
-
-    def tearDown(self):
-        return
-
-    def test_methods(self):
-        self.assertEqual(self.credentials.getAccessCode(), 'SOME_ACCESSCODE')
-        assert self.credentials.getLogin() is None
-        assert self.credentials.getPassword() is None
-        return
-
-class FakePluggableAuth(object):
-    prefix = 'foo'
-
-class PrincipalFactoryTest(unittest.TestCase):
-
-    def setUp(self):
-        self.info = ApplicantPrincipalInfo('APP-1-1234567890')
-        return
-
-    def tearDown(self):
-        pass
-
-    def test_principalFactory_interface(self):
-        verify.verifyClass(IAuthenticatedPrincipalFactory,
-                           AuthenticatedApplicantPrincipalFactory
-                           )
-        return
-
-    def test_principalFactory_create(self):
-        factory = AuthenticatedApplicantPrincipalFactory(self.info, None)
-
-        assert factory.info is self.info
-        assert factory.request is None
-        return
-
-    def test_principalFactory_call_w_prefix(self):
-        factory = AuthenticatedApplicantPrincipalFactory(self.info, None)
-        principal = factory(FakePluggableAuth())
-
-        assert isinstance(principal, ApplicantPrincipal)
-        self.assertEqual(principal.__repr__(),
-                         "ApplicantPrincipal('foo.APP-1-1234567890')")
-        self.assertEqual(principal.id, 'foo.APP-1-1234567890')
-        return
-
-    def test_principalFactory_call_wo_prefix(self):
-        factory = AuthenticatedApplicantPrincipalFactory(self.info, None)
-        fake_auth = FakePluggableAuth()
-        fake_auth.prefix = None
-        principal = factory(fake_auth)
-        self.assertEqual(principal.id, 'APP-1-1234567890')
-        return
-
-class PAUSetupTest(FunctionalTestCase):
-    # Check correct setup of authentication components in the
-    # applicants subpackage.
-
-    # When a university is created, we want by default have our local
-    # authentication components (an authenticator plugin and a
-    # credentials plugin) to be registered with the local PAU. Admins
-    # can remove these components on the fly later-on if they wish.
+class FunctionalApplicantAuthTests(ApplicantsFullSetup):
 
     layer = FunctionalLayer
 
     def setUp(self):
-        super(PAUSetupTest, self).setUp()
-
-        # Setup a sample site for each test
-        app = University()
-        self.dc_root = tempfile.mkdtemp()
-        app['datacenter'].setStoragePath(self.dc_root)
-
-        # Prepopulate the ZODB...
-        self.getRootFolder()['app'] = app
-        self.app = self.getRootFolder()['app']
-        self.browser = Browser()
-        self.browser.handleErrors = False
+        super(FunctionalApplicantAuthTests, self).setUp()
+        return
 
     def tearDown(self):
-        super(PAUSetupTest, self).tearDown()
-        shutil.rmtree(self.dc_root)
-
-    def test_extra_auth_plugins_installed(self):
-        # Check whether the auth plugins defined in here are setup
-        # automatically when a university is created
-
-        # Get the PAU responsible for the local site ('app')
-        pau = getUtility(IAuthentication, context=self.app)
-        cred_plugins = pau.getCredentialsPlugins()
-        auth_plugins = pau.getAuthenticatorPlugins()
-        cred_names = [name for name, plugin in cred_plugins]
-        auth_names = [name for name, plugin in auth_plugins]
-
-        # Make sure our local ApplicantsAuthenticatorPlugin is registered...
-        self.assertTrue('applicants' in auth_names)
-        # Make sure our local WAeUPApplicantCredentialsPlugin is registered...
-        self.assertTrue('applicant_credentials' in cred_names)
+        super(FunctionalApplicantAuthTests, self).tearDown()
         return
-
-class FakePAU(object):
-    credentialsPlugins = ()
-    authenticatorPlugins = ()
-
-class ApplicantsAuthenticatorSetupTests(FunctionalTestCase):
-
-    layer = FunctionalLayer
-
-    def test_ifaces(self):
-        # make sure we fullfill the interface promises
-        obj = ApplicantsAuthenticatorSetup()
-        verify.verifyClass(IAuthPluginUtility, ApplicantsAuthenticatorSetup)
-        verify.verifyObject(IAuthPluginUtility, obj)
-        return
-
-    def test_utility_available(self):
-        # we can get an applicant auth utility by lookup
-        util = queryUtility(IAuthPluginUtility,
-                            name='applicants_auth_setup')
-        self.assertTrue(util is not None)
-        return
-
-    def test_register(self):
-        # make sure we can register additional components
-        pau = FakePAU()
-        result = ApplicantsAuthenticatorSetup().register(pau)
-        self.assertEqual(
-            result.credentialsPlugins, ('applicant_credentials',))
-        self.assertEqual(
-            result.authenticatorPlugins, ('applicants',))
-        return
-
-    def test_unregister(self):
-        # make sure we can unregister applicant auth components
-        pau = FakePAU()
-        util = ApplicantsAuthenticatorSetup()
-        pau = util.register(pau)
-        result = util.unregister(pau)
-        self.assertEqual(
-            result.credentialsPlugins, ())
-        self.assertEqual(
-            result.authenticatorPlugins, ())
-        return
Index: /main/waeup.sirp/trunk/src/waeup/sirp/applicants/tests/test_browser.py
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/applicants/tests/test_browser.py	(revision 7239)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/applicants/tests/test_browser.py	(revision 7240)
@@ -33,5 +33,6 @@
 from waeup.sirp.applicants.container import ApplicantsContainer
 from waeup.sirp.applicants.applicant import Applicant
-from waeup.sirp.interfaces import IExtFileStore, IFileStoreNameChooser
+from waeup.sirp.interfaces import (
+    IExtFileStore, IFileStoreNameChooser, IUserAccount)
 from waeup.sirp.university.faculty import Faculty
 from waeup.sirp.university.department import Department
@@ -68,4 +69,5 @@
         setSite(app)
 
+        self.login_path = 'http://localhost/app/login'
         self.root_path = 'http://localhost/app/applicants'
         self.manage_root_path = self.root_path + '/@@manage'
@@ -77,4 +79,5 @@
         applicantscontainer = ApplicantsContainer()
         applicantscontainer.ac_prefix = 'APP'
+        applicantscontainer.code = u'app2009'
         applicantscontainer.prefix = 'app'
         applicantscontainer.year = 2009
@@ -84,4 +87,5 @@
         applicantscontainer.enddate = date.today() + delta
         self.app['applicants']['app2009'] = applicantscontainer
+        self.applicantscontainer = self.app['applicants']['app2009']
 
         # Populate university
@@ -112,8 +116,36 @@
 
         # Add an applicant
-        self.applicant = Applicant()
-        self.pin_applicant = unicode(self.pins[1])
-        self.applicant.access_code = self.pin_applicant
-        app['applicants']['app2009'][self.pin_applicant] = self.applicant
+        self.applicant = Applicant(container=applicantscontainer)
+        app['applicants']['app2009'][
+            self.applicant.application_number] = self.applicant
+        IUserAccount(
+            self.app['applicants']['app2009'][
+            self.applicant.application_number]).setPassword('apwd')
+        self.manage_path = 'http://localhost/app/applicants/%s/%s/%s' % (
+            'app2009', self.applicant.application_number, 'manage')
+        self.edit_path = 'http://localhost/app/applicants/%s/%s/%s' % (
+            'app2009', self.applicant.application_number, 'edit')
+        self.view_path = 'http://localhost/app/applicants/%s/%s' % (
+            'app2009', self.applicant.application_number)
+
+    def login(self):
+        # Perform an applicant login. This creates an applicant record.
+        #
+        # This helper also sets `self.applicant`, which is the
+        # applicant object created.
+        self.browser.open(self.login_path)
+        self.browser.getControl(name="form.login").value = self.applicant.applicant_id
+        self.browser.getControl(name="form.password").value = 'apwd'
+        self.browser.getControl("Login").click()
+
+    def fill_correct_values(self):
+        # Fill the edit form with suitable values
+        self.browser.getControl(name="form.firstname").value = 'John'
+        self.browser.getControl(name="form.lastname").value = 'Tester'
+        self.browser.getControl(name="form.course1").value = ['CERT1']
+        self.browser.getControl(name="form.date_of_birth").value = '09/09/1988'
+        self.browser.getControl(name="form.lga").value = ['foreigner']
+        self.browser.getControl(name="form.sex").value = ['m']
+        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
 
     def tearDown(self):
@@ -253,34 +285,22 @@
         self.assertEqual(self.browser.headers['Status'], '200 Ok')
         self.assertEqual(self.browser.url, self.add_applicant_path)
-        self.browser.getControl(name="form.ac_series").value = self.existing_series
-        self.browser.getControl(name="form.ac_number").value = self.existing_number
+        self.browser.getControl(name="form.firstname").value = 'Albert'
+        self.browser.getControl(name="form.lastname").value = 'Einstein'
+        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
         self.browser.getControl("Create application record").click()
         self.assertTrue('Application initialized' in self.browser.contents)
-        self.browser.open(self.add_applicant_path)
-        self.browser.getControl(name="form.ac_series").value = '123'
-        self.browser.getControl(name="form.ac_number").value = '456'
-        self.browser.getControl("Create application record").click()
-        self.assertTrue('is not a valid access code' in self.browser.contents)
         self.browser.open(self.container_manage_path)
         self.assertEqual(self.browser.headers['Status'], '200 Ok')
         ctrl = self.browser.getControl(name='val_id')
-        ctrl.getControl(value=self.existing_pin).selected = True
+        value = ctrl.options[0]
+        ctrl.getControl(value=value).selected = True
         self.browser.getControl("Remove selected", index=0).click()
         self.assertTrue('Successfully removed:' in self.browser.contents)
         self.browser.open(self.add_applicant_path)
-        existing_pin = self.pins[2]
-        parts = existing_pin.split('-')[1:]
-        existing_series, existing_number = parts
-        self.browser.getControl(name="form.ac_series").value = existing_series
-        self.browser.getControl(name="form.ac_number").value = existing_number
+        self.browser.getControl(name="form.firstname").value = 'Albert'
+        self.browser.getControl(name="form.lastname").value = 'Einstein'
+        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
         self.browser.getControl("Create application record").click()
         self.assertTrue('Application initialized' in self.browser.contents)
-        self.browser.open(self.container_manage_path)
-        self.assertTrue(existing_pin in self.browser.contents)
-        del self.app['applicants']['app2009'][existing_pin]
-        ctrl = self.browser.getControl(name='val_id')
-        ctrl.getControl(value=existing_pin).selected = True
-        self.browser.getControl("Remove selected", index=0).click()
-        self.assertMatches('...Could not delete...', self.browser.contents)
         return
 
@@ -288,76 +308,30 @@
         # Managers can manage applicants
         self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
-        self.applicant_path = 'http://localhost/app/applicants/app2009/%s' % self.pin_applicant
-        self.applicant_view_path = self.applicant_path + '/index'
-        self.applicant_manage_path = self.applicant_path + '/manage'
-        self.applicant_slip_path = self.applicant_path + '/application_slip.pdf'
-        self.browser.open(self.applicant_manage_path)
-        self.assertEqual(self.browser.headers['Status'], '200 Ok')
-        self.browser.getControl(name="form.email").value = 'abc'
-        self.browser.getControl("Save").click()
-        self.assertMatches('...Invalid email address...', self.browser.contents)
-        self.browser.getControl(name="form.email").value = 'abc@def.gh'
-        self.browser.getControl(name="form.firstname").value = 'John'
-        self.browser.getControl(name="form.lastname").value = 'Tester'
-        self.browser.getControl(name="form.course1").value = ['CERT1']
-        self.browser.getControl(name="form.course_admitted").value = ['CERT1']
-        self.browser.getControl(name="form.date_of_birth").value = '09/09/1988'
-        self.browser.getControl(name="form.lga").value = ['foreigner']
-        self.browser.getControl(name="form.sex").value = ['m']
+        self.slip_path = self.view_path + '/application_slip.pdf'
+        self.browser.open(self.manage_path)
+        self.assertEqual(self.browser.headers['Status'], '200 Ok')
+        self.fill_correct_values()
         self.browser.getControl(name="transition").value = ['start']
         self.browser.getControl("Save").click()
         self.assertMatches('...Form has been saved...', self.browser.contents)
         self.assertMatches('...Application started by zope.mgr...', self.browser.contents)
-        self.browser.open(self.applicant_view_path)
-        self.assertEqual(self.browser.headers['Status'], '200 Ok')
-        self.browser.open(self.applicant_slip_path)
+        self.browser.open(self.view_path)
+        self.assertEqual(self.browser.headers['Status'], '200 Ok')
+        self.browser.open(self.slip_path)
         self.assertEqual(self.browser.headers['Status'], '200 Ok')
         self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
-        self.browser.open(self.applicant_manage_path)
+        self.browser.open(self.manage_path)
         self.browser.getControl(name="form.course_admitted").value = []
         self.browser.getControl("Save").click()
-        self.browser.open(self.applicant_slip_path)
+        self.browser.open(self.slip_path)
         self.assertEqual(self.browser.headers['Status'], '200 Ok')
         self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
-        return
-
-    def test_view_applicant(self):
-        # Applicants can login and view their application
-        self.login_path = 'http://localhost/app/applicants/app2009/login'
-        self.browser.open(self.login_path)
-        pin = self.pins[2]
-        parts = pin.split('-')[1:]
-        existing_series, existing_number = parts
-        ac_series = self.browser.getControl(name="form.ac_series")
-        ac_series.value = existing_series
-        ac_number = self.browser.getControl(name="form.ac_number")
-        ac_number.value = existing_number
-        self.browser.getControl(name="SUBMIT").click()
-        self.assertTrue(self.browser.url != self.login_path)
-        self.assertEqual(self.browser.headers['Status'], '200 Ok')
         return
 
     def test_passport_edit_view(self):
         # We get a default image after login
-        login_path = 'http://localhost/app/applicants/app2009/login'
-        self.browser.open(login_path)
-        pin = self.pins[2]
-        parts = pin.split('-')[1:]
-        existing_series, existing_number = parts
-        ac_series = self.browser.getControl(name="form.ac_series")
-        ac_series.value = existing_series
-        ac_number = self.browser.getControl(name="form.ac_number")
-        ac_number.value = existing_number
-        self.browser.getControl(name="SUBMIT").click()
-        pin = self.pins[2]
-        #appl = self.getRootFolder()['app']['applicants']['app2009']
-        #appl = appl[pin]
-        #passp = appl.passport
-        #passp_len = len(passp.file.read())
-        #self.assertEqual(passp_len, PH_LEN)
-        #image_url = "%s/%s" % (self.browser.url, 'placeholder.jpg')
-        image_url = "%s/%s" % (self.browser.url, 'passport.jpg')
-        #self.browser.open(image_url)
-        self.browser.open('passport.jpg')
+        self.browser.open(self.login_path)
+        self.login()
+        self.browser.open(self.browser.url + '/passport.jpg')
         self.assertEqual(self.browser.headers['status'], '200 Ok')
         self.assertEqual(self.browser.headers['content-type'], 'image/jpeg')
@@ -366,25 +340,12 @@
             self.browser.headers['content-length'], str(PH_LEN))
 
-
     def test_edit_applicant(self):
         # Applicants can edit their record
-        self.login_path = 'http://localhost/app/applicants/app2009/login'
         self.browser.open(self.login_path)
-        pin = self.pins[2]
-        parts = pin.split('-')[1:]
-        existing_series, existing_number = parts
-        ac_series = self.browser.getControl(name="form.ac_series")
-        ac_series.value = existing_series
-        ac_number = self.browser.getControl(name="form.ac_number")
-        ac_number.value = existing_number
-        self.browser.getControl(name="SUBMIT").click()
+        self.login()
+        self.browser.open(self.edit_path)
         self.assertTrue(self.browser.url != self.login_path)
         self.assertEqual(self.browser.headers['Status'], '200 Ok')
-        self.browser.getControl(name="form.firstname").value = 'John'
-        self.browser.getControl(name="form.lastname").value = 'Tester'
-        self.browser.getControl(name="form.course1").value = ['CERT1']
-        self.browser.getControl(name="form.date_of_birth").value = '09/09/1988'
-        self.browser.getControl(name="form.lga").value = ['foreigner']
-        self.browser.getControl(name="form.sex").value = ['m']
+        self.fill_correct_values()
         self.browser.getControl("Save").click()
         self.assertMatches('...Form has been saved...', self.browser.contents)
@@ -421,213 +382,8 @@
         return
 
-    # We have no local roles yet
-    #def test_local_roles_add_delete(self):
-    #    # Managers can assign and delete local roles of applicants containers
-    #    myusers = self.app['users']
-    #    myusers.addUser('bob', 'bobssecret')
-    #    self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
-    #    self.browser.open(self.manage_container_path)
-    #    self.browser.getControl(name="user").value = ['bob']
-    #    self.browser.getControl(name="local_role").value = [
-    #        'waeup.local.ApplicationsOfficer']
-    #    self.browser.getControl("Add local role").click()
-    #    self.assertTrue('<td>bob</td>' in self.browser.contents)
-    #    ctrl = self.browser.getControl(name='role_id')
-    #    ctrl.getControl(value='bob|waeup.ApplicationsOfficer').selected = True
-    #    self.browser.getControl("Remove selected local roles").click()
-    #    self.assertTrue('Successfully removed:' in self.browser.contents)
-    #    self.assertFalse('<td>bob</td>' in self.browser.contents)
-    #    return
-
-class LoginTest(FunctionalTestCase):
-    # Here we check login view of applicants containers.
-    #
-    # Tests in here do only cover login attempts without any PINs
-    # created before.
-
-    layer = FunctionalLayer
-
-    def setUp(self):
-        super(LoginTest, self).setUp()
-
-        # Setup a sample site for each test
-        app = University()
-        self.dc_root = tempfile.mkdtemp()
-        app['datacenter'].setStoragePath(self.dc_root)
-        self.login_path = 'http://localhost/app/applicants/testapplicants/login'
-
-        # Add an applicants container where we can login (or not)
-        applicantscontainer = ApplicantsContainer()
-        applicantscontainer.ac_prefix = 'APP'
-        delta = timedelta(days=10)
-        applicantscontainer.startdate = date.today() - delta
-        applicantscontainer.enddate = date.today() + delta
-        app['applicants']['testapplicants'] = applicantscontainer
-
-        # Put the prepopulated site into test ZODB and prepare test
-        # browser
-        self.getRootFolder()['app'] = app
-        self.browser = Browser()
-        self.browser.handleErrors = False
-
-    def tearDown(self):
-        super(LoginTest, self).tearDown()
-        shutil.rmtree(self.dc_root)
-
-    def test_anonymous_access(self):
-        # Anonymous users can access a login page
-        self.browser.open(self.login_path)
-        self.assertEqual(self.browser.headers['Status'], '200 Ok')
-        return
-
-    def test_anonymous_invalid_creds(self):
-        # Anonymous users giving invalid credentials stay at the page
-        self.browser.open(self.login_path)
-        # We do not give credentials but send the form as-is
-        submit = self.browser.getControl(name='SUBMIT')
-        submit.click()
-        # We are still at the same page...
-        self.assertEqual(self.browser.url, self.login_path)
-        self.assertEqual(self.browser.headers['Status'], '200 Ok')
-        return
-
-    def test_anonymous_invalid_creds_warning(self):
-        # Entering wrong credentials will yield a warning
-        self.browser.open(self.login_path)
-        # We do not give credentials but send the form as-is
-        submit = self.browser.getControl(name='SUBMIT')
-        submit.click()
-        self.assertTrue(
-            'Entered credentials are invalid' in self.browser.contents)
-        return
-
-    def test_manager_no_warnings(self):
-        # Browsing the login screen as a manager, won't raise warnings
-        # Authenticate ourself as manager
-        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
-        self.browser.open(self.login_path)
-        # Submit the form w/o any credentials
-        self.browser.getControl(name="SUBMIT").click()
-        self.assertTrue(
-            'Entered credentials are invalid' not in self.browser.contents)
-        return
-
-    def test_manager_no_redirect(self):
-        # Browsing the login screen as a manager won't trigger a redirect
-        # Authenticate ourself as manager
-        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
-        self.browser.open(self.login_path)
-        # Submit the form w/o any credentials
-        self.browser.getControl(name="SUBMIT").click()
-        self.assertEqual(self.browser.url, self.login_path)
-        return
-
-    def test_display_entered_values(self):
-        # After submit the entered values are displayed in the form
-        self.browser.open(self.login_path)
-        # Enter some value we can look for after submit
-        ac_series = self.browser.getControl(name="form.ac_series")
-        ac_series.value = '666'
-        self.browser.getControl(name="SUBMIT").click()
-        self.assertTrue('666' in self.browser.contents)
-        return
-
-class LoginTestWithPINs(LoginTest):
-    # Here we check login view of applicants containers with PINs provided.
-
-    # As setting up pins is time-consuming we only set them up when
-    # really needed (i.e. in this separate TestCase).
-
-    layer = FunctionalLayer
-
-    def setUp(self):
-        super(LoginTestWithPINs, self).setUp()
-
-        # Create 5 access codes with prefix'FOO' and cost 9.99 each
-        pin_container = self.getRootFolder()['app']['accesscodes']
-        pin_container.createBatch(
-            datetime.now(), 'some_userid', 'APP', 9.99, 5)
-        pins = pin_container[pin_container.keys()[0]].values()
-        self.pins = [x.representation for x in pins]
-        self.existing_pin = self.pins[0]
-        parts = self.existing_pin.split('-')[1:]
-        self.existing_series, self.existing_number = parts
-        self.browser.handleErrors = False
-
-    def tearDown(self):
-        super(LoginTestWithPINs, self).tearDown()
-
-    def test_anonymous_valid_login(self):
-        # If we enter valid credentials, we get to the applicants form
-        self.browser.open(self.login_path)
-        # Enter some value we can look for after submit
-        ac_series = self.browser.getControl(name="form.ac_series")
-        ac_series.value = self.existing_series
-        ac_number = self.browser.getControl(name="form.ac_number")
-        ac_number.value = self.existing_number
-        self.browser.getControl(name="SUBMIT").click()
-        # We should be redirected to applicants form.
-        self.assertTrue(self.browser.url != self.login_path)
-        # Applicants see their Access Code in the contact form
-        #self.browser.getLink("Contact").click()
-        #self.assertTrue(
-        #    'Access Code:' in self.browser.contents)
-        return
-
-    def test_anonymous_invalid_login(self):
-        # If we enter wrong credentials we won't get far
-        self.browser.open(self.login_path)
-        # Enter some value we can look for after submit
-        ac_series = self.browser.getControl(name="form.ac_series")
-        ac_series.value = 'illegal series'
-        ac_number = self.browser.getControl(name="form.ac_number")
-        ac_number.value = 'invalid number'
-        self.browser.getControl(name="SUBMIT").click()
-        # We get a warning message
-        self.assertTrue(
-            'Entered credentials are invalid' in self.browser.contents)
-        # We stay at the login page (no redirect)
-        self.assertTrue(self.browser.url == self.login_path)
-        return
-
 class ApplicantsPassportTests(ApplicantsFullSetup):
     # Tests for uploading/browsing the passport image of appplicants
 
     layer = FunctionalLayer
-
-    def setUp(self):
-        super(ApplicantsPassportTests, self).setUp()
-        self.login_path = 'http://localhost/app/applicants/app2009/login'
-        self.pin = self.pins[2]
-        self.existing_series, self.existing_number = self.pin.split('-')[1:]
-        self.edit_path = 'http://localhost/app/applicants/app2009/%s/edit' % (
-            self.pin)
-        self.manage_path = 'http://localhost/app/applicants/%s/%s/%s' % (
-            'app2009', self.pin, 'manage')
-
-    def tearDown(self):
-        super(ApplicantsPassportTests, self).tearDown()
-
-    def login(self):
-        # Perform an applicant login. This creates an applicant record.
-        #
-        # This helper also sets `self.applicant`, which is the
-        # applicant object created.
-        self.browser.open(self.login_path)
-        ac_series = self.browser.getControl(name="form.ac_series")
-        ac_series.value = self.existing_series
-        ac_number = self.browser.getControl(name="form.ac_number")
-        ac_number.value = self.existing_number
-        self.browser.getControl(name="SUBMIT").click()
-        self.applicant = self.app['applicants']['app2009'][self.pin]
-
-    def fill_correct_values(self):
-        # Fill the edit form with suitable values
-        self.browser.getControl(name="form.firstname").value = 'John'
-        self.browser.getControl(name="form.lastname").value = 'Tester'
-        self.browser.getControl(name="form.course1").value = ['CERT1']
-        self.browser.getControl(name="form.date_of_birth").value = '09/09/1988'
-        self.browser.getControl(name="form.lga").value = ['foreigner']
-        self.browser.getControl(name="form.sex").value = ['m']
 
     def image_url(self, filename):
@@ -638,5 +394,21 @@
         #import pdb; pdb.set_trace()
         self.login()
-        self.assertEqual(self.browser.url, self.edit_path)
+        self.assertEqual(self.browser.url, self.view_path)
+        self.browser.open(self.edit_path)
+        # There is a correct <img> link included
+        self.assertTrue(
+              '<img src="passport.jpg" />' in self.browser.contents)
+        # Browsing the link shows a real image
+        self.browser.open(self.image_url('passport.jpg'))
+        self.assertEqual(
+            self.browser.headers['content-type'], 'image/jpeg')
+        self.assertEqual(len(self.browser.contents), PH_LEN)
+
+    def test_after_submit_default_browsable(self):
+        # After submitting an applicant form the default image is
+        # still visible
+        self.login()
+        self.browser.open(self.edit_path)
+        self.browser.getControl("Save").click() # submit form
         # There is a correct <img> link included
         self.assertTrue(
@@ -648,22 +420,9 @@
         self.assertEqual(len(self.browser.contents), PH_LEN)
 
-    def test_after_submit_default_browsable(self):
-        # After submitting an applicant form the default image is
-        # still visible
-        self.login()
-        self.browser.getControl("Save").click() # submit form
-        # There is a correct <img> link included
-        self.assertTrue(
-            '<img src="passport.jpg" />' in self.browser.contents)
-        # Browsing the link shows a real image
-        self.browser.open(self.image_url('passport.jpg'))
-        self.assertEqual(
-            self.browser.headers['content-type'], 'image/jpeg')
-        self.assertEqual(len(self.browser.contents), PH_LEN)
-
     def test_uploaded_image_respects_file_size_restriction(self):
         # When we upload an image that is too big ( > 10 KB) we will
         # get an error message
         self.login()
+        self.browser.open(self.edit_path)
         # Create a pseudo image file and select it to be uploaded in form
         photo_content = 'A' * 1024 * 21  # A string of 21 KB size
@@ -694,4 +453,5 @@
         # even if there are still errors in the form
         self.login()
+        self.browser.open(self.edit_path)
         # Create a pseudo image file and select it to be uploaded in form
         photo_content = 'I pretend to be a graphics file'
@@ -714,4 +474,5 @@
         # stored in an imagestorage
         self.login()
+        self.browser.open(self.edit_path)
         # Create a pseudo image file and select it to be uploaded in form
         pseudo_image = StringIO('I pretend to be a graphics file')
@@ -731,4 +492,5 @@
         # if there are no errors in form
         self.login()
+        self.browser.open(self.edit_path)
         self.fill_correct_values() # fill other fields with correct values
         # Create a pseudo image file and select it to be uploaded in form
@@ -751,4 +513,5 @@
         # stored in an imagestorage if form contains no errors
         self.login()
+        self.browser.open(self.edit_path)
         self.fill_correct_values() # fill other fields with correct values
         # Create a pseudo image file and select it to be uploaded in form
@@ -768,5 +531,6 @@
         # Make sure uploaded images do really differ if we eject a
         # change notfication (and do not if we don't)
-        self.login() # Create applicant form
+        self.login()
+        self.browser.open(self.edit_path)
         self.fill_correct_values() # fill other fields with correct values
         self.browser.getControl("Save").click() # submit form
@@ -795,5 +559,6 @@
         # Make sure that a correctly filled form with passport picture
         # can be submitted
-        self.login() # Create applicant form
+        self.login()
+        self.browser.getLink("Edit application record").click()
         self.fill_correct_values() # fill other fields with correct values
         # Create a pseudo image file and select it to be uploaded in form
@@ -814,4 +579,5 @@
         # Now do the whole thing again but with correct state
         self.login()
+        self.browser.open(self.edit_path)
         self.fill_correct_values()
         pseudo_image = StringIO('I pretend to be a graphics file')
@@ -836,5 +602,6 @@
     def test_locking(self):
         # Make sure that locked forms can't be submitted
-        self.login() # Create applicant form
+        self.login()
+        self.browser.open(self.edit_path)
         self.fill_correct_values() # fill other fields with correct values
         # Create a pseudo image file and select it to be uploaded in form
@@ -844,15 +611,6 @@
         file_ctrl.add_file(pseudo_image, filename='myphoto.jpg')
         self.browser.getControl("Save").click()
-        self.browser.getLink("Logout").click()
-
-        # Login as manager and lock the form
-        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
-        self.browser.open(self.manage_path)
-        self.browser.getControl(name="form.locked").value = True
-        self.browser.getControl("Save").click()
-        self.browser.getLink("Logout").click()
-
-        # Login as applicant again and try to open the edit form
-        self.login()
+        # Now we lock the form
+        self.applicant.locked = True
         self.browser.open(self.edit_path)
         self.assertEqual(self.browser.headers['Status'], '200 Ok')
Index: /main/waeup.sirp/trunk/src/waeup/sirp/applicants/tests/test_catalog.py
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/applicants/tests/test_catalog.py	(revision 7239)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/applicants/tests/test_catalog.py	(revision 7240)
@@ -58,10 +58,10 @@
         # Create an applicant in an applicants container
         self.container = ApplicantsContainer()
+        self.container.code = u"mystuff"
         setSite(self.app)
         self.app['applicants']['mystuff'] = self.container
-        self.applicant = Applicant()
-        self.ac = u'FOO-666-123456789'
-        self.applicant.access_code = self.ac
-        self.app['applicants']['mystuff'][self.ac] = self.applicant
+        self.applicant = Applicant(container=self.container)
+        self.app['applicants']['mystuff'][
+            self.applicant.application_number] = self.applicant
         return
 
@@ -80,9 +80,9 @@
         self.create_applicant()
         q = getUtility(IQuery)
-        subquery = Eq(('applicants_catalog', 'access_code'), self.ac)
+        subquery = Eq(('applicants_catalog', 'applicant_id'),
+            self.applicant.applicant_id)
         results = list(q.searchResults(subquery))
         self.assertEqual(len(results), 1)
-
         result_applicant = results[0]
         self.assertTrue(isinstance(result_applicant, Applicant))
-        self.assertEqual(result_applicant.access_code, self.ac)
+        self.assertEqual(result_applicant.applicant_id, self.applicant.applicant_id)
Index: /main/waeup.sirp/trunk/src/waeup/sirp/authentication.py
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/authentication.py	(revision 7239)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/authentication.py	(revision 7240)
@@ -76,9 +76,9 @@
 
     A SIRP principal info is created with id, login, title, description,
-    phone and email.
+    phone, email and user_type.
     """
     grok.implements(ISIRPPrincipalInfo)
 
-    def __init__(self, id, title, description, email, phone):
+    def __init__(self, id, title, description, email, phone, user_type):
         self.id = id
         self.title = title
@@ -86,4 +86,5 @@
         self.email = email
         self.phone = phone
+        self.user_type = user_type
         self.credentialsPlugin = None
         self.authenticatorPlugin = None
@@ -92,5 +93,5 @@
     """A portal principal.
 
-    SIRP principals provide an extra `email` and `phone`
+    SIRP principals provide an extra `email`, `phone` and `user_type`
     attribute extending ordinary principals.
     """
@@ -99,5 +100,5 @@
 
     def __init__(self, id, title=u'', description=u'', email=u'',
-                 phone=None, prefix=None):
+                 phone=None, user_type=u'', prefix=None):
         self.id = id
         if prefix is not None:
@@ -108,4 +109,5 @@
         self.email = email
         self.phone = phone
+        self.user_type = user_type
 
     def __repr__(self):
@@ -134,4 +136,5 @@
             self.info.email,
             self.info.phone,
+            self.info.user_type,
             authentication.prefix,
             )
@@ -147,5 +150,5 @@
 
     def __init__(self, name, password, title=None, description=None,
-                 email=None, phone=None, roles = []):
+                 email=None, phone=None, user_type=None, roles = []):
         self.name = name
         if title is None:
@@ -157,4 +160,5 @@
         self.email = email
         self.phone = phone
+        self.user_type = user_type
         self.setPassword(password)
         #self.setSiteRolesForPrincipal(roles)
@@ -211,8 +215,8 @@
         if not isinstance(credentials, dict):
             return None
+        
         if not ('login' in credentials and 'password' in credentials):
             return None
         account = self.getAccount(credentials['login'])
-
         if account is None:
             return None
@@ -223,5 +227,6 @@
                              description=account.description,
                              email=account.email,
-                             phone=account.phone)
+                             phone=account.phone,
+                             user_type=u'user')
 
     def principalInfo(self, id):
@@ -233,5 +238,6 @@
                              description=account.description,
                              email=account.email,
-                             phone=account.phone)
+                             phone=account.phone,
+                             user_type=u'user')
 
     def getAccount(self, login):
Index: /main/waeup.sirp/trunk/src/waeup/sirp/browser/layout.py
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/browser/layout.py	(revision 7239)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/browser/layout.py	(revision 7240)
@@ -32,4 +32,5 @@
 from waeup.sirp.browser.theming import get_all_themes, WAeUPThemeGray1
 from waeup.sirp.students.interfaces import IStudentNavigation
+from waeup.sirp.applicants.interfaces import IApplicant
 from waeup.sirp.authentication import get_principal_role_manager
 
@@ -163,6 +164,4 @@
         if usertitle == 'Unauthenticated User':
             return u'Anonymous User'
-        elif usertitle == 'Applicant':
-            return self.request.principal.id
         return usertitle
 
@@ -172,4 +171,16 @@
         userid = self.request.principal.id
         return userid
+
+    def isStudent(self):
+        usertype = getattr(self.request.principal, 'user_type', None)
+        if not usertype:
+            return False
+        return self.request.principal.user_type == 'student'
+
+    def isApplicant(self):
+        usertype = getattr(self.request.principal, 'user_type', None)
+        if not usertype:
+            return False
+        return self.request.principal.user_type == 'applicant'
 
     def getStudentName(self):
@@ -180,9 +191,10 @@
         return
 
-    def isStudent(self):
-        prm = get_principal_role_manager()
-        roles = [x[0] for x in
-            prm.getRolesForPrincipal(self.request.principal.id)]
-        return 'waeup.Student' in roles
+    def getApplicantName(self):
+        """Return the applicant name.
+        """
+        if IApplicant.providedBy(self.context):
+            return self.context.fullname
+        return
 
     def update(self):
@@ -209,5 +221,5 @@
 
     def render(self):
-        if self.isStudent() or not self.isAuthenticated():
+        if self.isStudent() or self.isApplicant() or not self.isAuthenticated():
             return self.studenttemp.render(self)
         return self.stafftemp.render(self)
Index: /main/waeup.sirp/trunk/src/waeup/sirp/browser/pages.py
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/browser/pages.py	(revision 7239)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/browser/pages.py	(revision 7240)
@@ -150,9 +150,4 @@
     camefrom = None
 
-    def isStudent(self):
-        prm = get_principal_role_manager()
-        roles = [x[0] for x in prm.getRolesForPrincipal(self.request.principal.id)]
-        return 'waeup.Student' in roles
-
     def update(self, SUBMIT=None, camefrom=None):
         self.camefrom = camefrom
@@ -160,6 +155,12 @@
             if self.request.principal.id != 'zope.anybody':
                 self.flash('You logged in.')
-                if self.isStudent():
+                if self.request.principal.user_type == 'student':
                     rel_link = '/students/%s' % self.request.principal.id
+                    self.redirect(self.application_url() + rel_link)
+                    return
+                elif self.request.principal.user_type == 'applicant':
+                    container, application_number = self.request.principal.id.split('_')
+                    rel_link = '/applicants/%s/%s' % (
+                        container, application_number)
                     self.redirect(self.application_url() + rel_link)
                     return
@@ -222,8 +223,10 @@
             email_from = self.config.email_admin
         username = self.request.principal.id
+        usertype = getattr(self.request.principal,
+                           'user_type', 'system').title()
         body = data['body']
         email_to = self.config.email_admin
         subject = self.config.email_subject
-        success = send_mail(fullname,username,self.config.name,
+        success = send_mail(fullname,username,usertype,self.config.name,
                   body,email_from,email_to,subject)
         if success:
@@ -248,5 +251,6 @@
         email_to = self.config.email_admin
         subject = self.config.email_subject
-        success = send_mail(fullname,username,self.config.name,
+        usertype = u'Anonymous'
+        success = send_mail(fullname,username,usertype,self.config.name,
                   body,email_from,email_to,subject)
         if success:
@@ -467,8 +471,10 @@
             email_from = self.config.email_admin
         username = self.request.principal.id
+        usertype = getattr(self.request.principal,
+                           'user_type', 'system').title()
         body = data['body']
         email_to = self.context.email
         subject = self.config.email_subject
-        success = send_mail(fullname,username,self.config.name,
+        success = send_mail(fullname,username,usertype,self.config.name,
                   body,email_from,email_to,subject)
         if success:
Index: /main/waeup.sirp/trunk/src/waeup/sirp/browser/static/waeup-base.css
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/browser/static/waeup-base.css	(revision 7239)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/browser/static/waeup-base.css	(revision 7240)
@@ -70,5 +70,5 @@
 
 .studentmenu {
-  text-align: center;
+  text-align: left;
   line-height: 2;
 }
Index: /main/waeup.sirp/trunk/src/waeup/sirp/browser/templates/studentsitelayout.pt
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/browser/templates/studentsitelayout.pt	(revision 7239)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/browser/templates/studentsitelayout.pt	(revision 7240)
@@ -56,7 +56,13 @@
 
         <div class="yui-b">
-          <div class="block" tal:condition="layout/getStudentName">
+          <div class="block" >
             <div class="studentmenu">
-              <tal:left content="structure provider:top_student" />
+              <div tal:condition="layout/getStudentName"
+                   tal:replace="structure provider:top_student">
+                Student Links
+              </div>
+              <div tal:content="structure provider:actionbar">
+                Student Actions
+              </div>
             </div>
           </div>
@@ -89,5 +95,4 @@
                   <div tal:replace="structure view/content"> THE CONTENT
                   </div>
-                  <span tal:replace="structure provider:actionbar"></span>
                 </div>
               </div>
Index: /main/waeup.sirp/trunk/src/waeup/sirp/permissions.py
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/permissions.py	(revision 7239)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/permissions.py	(revision 7240)
@@ -115,7 +115,9 @@
                      'waeup.manageApplication', 'waeup.handleApplication',
                      'waeup.viewStudent', 'waeup.manageStudent', 'clearStudent',
-                     'waeup.uploadStudentFile', 'waeup.viewStudents',
+                     'waeup.uploadStudentFile', 
                      'waeup.viewHostels', 'waeup.manageHostels',
-                     'waeup.showStudents')
+                     'waeup.showStudents',
+                     'waeup.viewStudentsContainer','waeup.viewStudentsTab',
+                     )
 
 def get_all_roles():
Index: /main/waeup.sirp/trunk/src/waeup/sirp/students/authentication.py
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/students/authentication.py	(revision 7239)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/students/authentication.py	(revision 7240)
@@ -60,4 +60,8 @@
 
     @property
+    def user_type(self):
+        return u'student'
+
+    @property
     def description(self):
         return self.title
@@ -126,5 +130,4 @@
             return None
         account = self.getAccount(credentials['login'])
-
         if account is None:
             return None
@@ -135,5 +138,6 @@
                              description=account.description,
                              email=account.email,
-                             phone=account.phone)
+                             phone=account.phone,
+                             user_type=account.user_type)
 
     def principalInfo(self, id):
@@ -266,4 +270,5 @@
     """
     grok.implements(IAuthPluginUtility)
+    grok.name('students_auth_setup')
 
     def register(self, pau):
Index: /main/waeup.sirp/trunk/src/waeup/sirp/students/browser.py
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/students/browser.py	(revision 7239)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/students/browser.py	(revision 7240)
@@ -161,5 +161,5 @@
     grok.context(IStudentsContainer)
     grok.name('index')
-    grok.require('waeup.viewStudents')
+    grok.require('waeup.viewStudentsContainer')
     grok.template('containerpage')
     label = 'Student Section'
@@ -381,9 +381,10 @@
             email_from = self.config.email_admin
         username = self.request.principal.id
+        usertype = self.request.principal.user_type.title()
         body = data['body']
         #subject = u'Mail from SIRP'
         subject = data['subject']
         email_to = self.context.email
-        success = send_mail(fullname,username,self.config.name,
+        success = send_mail(fullname,username,usertype,self.config.name,
                   body,email_from,email_to,subject)
         if success:
@@ -1271,13 +1272,7 @@
         return
 
-    @property
-    def is_student(self):
-        prm = get_principal_role_manager()
-        roles = [x[0] for x in prm.getRolesForPrincipal(self.request.principal.id)]
-        return 'waeup.Student' in roles
-
     @grok.action('Remove selected')
     def delBedTickets(self, **data):
-        if self.is_student:
+        if getattr(self.request.principal, 'user_type', None) == 'student':
             self.flash('You are not allowed to remove bed tickets.')
             self.redirect(self.url(self.context))
@@ -1305,5 +1300,5 @@
     def selected_actions(self):
         sa = self.actions
-        if self.is_student:
+        if getattr(self.request.principal, 'user_type', None) == 'student':
             sa = [action for action in self.actions
                   if not action.label in self.officers_only_actions]
Index: /main/waeup.sirp/trunk/src/waeup/sirp/students/browser_templates/accommodationmanagepage.pt
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/students/browser_templates/accommodationmanagepage.pt	(revision 7239)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/students/browser_templates/accommodationmanagepage.pt	(revision 7240)
@@ -18,5 +18,5 @@
     <thead>
     <tr>
-      <th tal:condition="not: view/is_student">&nbsp;</th>
+      <th tal:condition="not: layout/isStudent">&nbsp;</th>
       <th>Session</th>
       <th>Booking Date</th>
@@ -27,5 +27,5 @@
     <tbody>
       <tr tal:repeat="cl context/values">
-         <td tal:condition="not: view/is_student">
+         <td tal:condition="not: layout/isStudent">
           <input type="checkbox"
     		 name="val_id"
Index: /main/waeup.sirp/trunk/src/waeup/sirp/students/interfaces.py
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/students/interfaces.py	(revision 7239)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/students/interfaces.py	(revision 7240)
@@ -123,5 +123,5 @@
 
     student_id = schema.TextLine(
-        title = u'Student ID',
+        title = u'Student Id',
         required = False,
         )
Index: /main/waeup.sirp/trunk/src/waeup/sirp/students/permissions.py
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/students/permissions.py	(revision 7239)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/students/permissions.py	(revision 7240)
@@ -29,6 +29,12 @@
     grok.name('waeup.viewStudent')
 
-class ViewStudents(grok.Permission):
-    grok.name('waeup.viewStudents')
+class ViewStudentsTab(grok.Permission):
+    grok.name('waeup.viewStudentsTab')
+
+class ViewMyStudentDataTab(grok.Permission):
+    grok.name('waeup.viewMyStudentDataTab')
+
+class ViewStudentsContainer(grok.Permission):
+    grok.name('waeup.viewStudentsContainer')
 
 class PayStudent(grok.Permission):
@@ -58,10 +64,11 @@
 class StudentRole(grok.Role):
     grok.name('waeup.Student')
-    grok.permissions('waeup.viewAcademics')
+    grok.permissions('waeup.viewAcademics', 'waeup.viewMyStudentDataTab')
 
 class StudentsOfficer(grok.Role):
     grok.name('waeup.StudentsOfficer')
     grok.title(u'Students Officer (view only)')
-    grok.permissions('waeup.viewStudent','waeup.viewStudents')
+    grok.permissions('waeup.viewStudent','waeup.viewStudents',
+          'waeup.viewStudentsTab', 'waeup.viewStudentsContainer')
 
 class StudentsManager(grok.Role):
Index: /main/waeup.sirp/trunk/src/waeup/sirp/students/tests/test_browser.py
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/students/tests/test_browser.py	(revision 7239)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/students/tests/test_browser.py	(revision 7240)
@@ -693,5 +693,5 @@
         self.browser.getControl(name="form.password").value = 'mrclearsecret'
         self.browser.getControl("Login").click()
-        self.assertTrue('You logged in' in self.browser.contents)
+        self.assertMatches('...You logged in...', self.browser.contents)
         # CO can see his roles
         self.browser.getLink("My Roles").click()
Index: /main/waeup.sirp/trunk/src/waeup/sirp/students/viewlets.py
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/students/viewlets.py	(revision 7239)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/students/viewlets.py	(revision 7240)
@@ -163,5 +163,5 @@
     grok.context(IWAeUPObject)
     grok.order(4)
-    grok.require('waeup.viewStudents')
+    grok.require('waeup.viewStudentsTab')
     grok.template('primarynavtab')
 
@@ -200,29 +200,9 @@
         return ''
 
-#class HomeTab(PrimaryStudentNavTab):
-#    """Home-tab in primary navigation.
-#    """
-#    grok.order(1)
-#    grok.require('waeup.Authenticated')
-#    pnav = 0
-#    tab_title = u'Home'
-
-#class ProspectusTab(PrimaryStudentNavTab):
-#    """Faculties-tab in primary navigation.
-#    """
-#    grok.order(2)
-#    grok.require('waeup.viewAcademics')
-#    pnav = 1
-#    tab_title = u'Prospectus'
-
-#    @property
-#    def link_target(self):
-#        return self.view.application_url('faculties')
-
-class MyDataTab(PrimaryStudentNavTab):
+class MyStudentDataTab(PrimaryStudentNavTab):
     """MyData-tab in primary navigation.
     """
     grok.order(3)
-    grok.require('waeup.Authenticated')
+    grok.require('waeup.viewMyStudentDataTab')
     pnav = 4
     tab_title = u'My Data'
Index: /main/waeup.sirp/trunk/src/waeup/sirp/utils/helpers.py
===================================================================
--- /main/waeup.sirp/trunk/src/waeup/sirp/utils/helpers.py	(revision 7239)
+++ /main/waeup.sirp/trunk/src/waeup/sirp/utils/helpers.py	(revision 7240)
@@ -481,5 +481,5 @@
     return account
 
-def send_mail(fullname,username,portal,body,email_from,email_to,subject):
+def send_mail(fullname,username,usertype,portal,body,email_from,email_to,subject):
     """Send an email with data provided by forms.
     """
@@ -487,9 +487,10 @@
     text = """Fullname: %s
 User Id: %s
+User Type: %s
 Portal: %s
 
 %s
 """
-    msg = MIMEText(text % (fullname,username,portal,body))
+    msg = MIMEText(text % (fullname,username,usertype,portal,body))
     msg['From'] = '%s <%s>' % (fullname,email_from)
     msg['To'] = email_to
