Index: main/waeup.sirp/trunk/src/waeup/sirp/applicants/__init__.py
===================================================================
--- main/waeup.sirp/trunk/src/waeup/sirp/applicants/__init__.py	(revision 7346)
+++ main/waeup.sirp/trunk/src/waeup/sirp/applicants/__init__.py	(revision 7347)
@@ -10,4 +10,6 @@
     ApplicantsRoot, get_applicant_data, application_exists,
     )
+from waeup.sirp.applicants.dynamicroles import (
+    ApplicantPrincipalRoleManager,)
 
 __all__ = [
@@ -16,4 +18,5 @@
     'ApplicantImageNameChooser',
     'ApplicantImageStoreHandler',
+    'ApplicantPrincipalRoleManager',
     'ApplicantsContainer',
     'ApplicantsRoot',
Index: main/waeup.sirp/trunk/src/waeup/sirp/applicants/applicant.py
===================================================================
--- main/waeup.sirp/trunk/src/waeup/sirp/applicants/applicant.py	(revision 7346)
+++ main/waeup.sirp/trunk/src/waeup/sirp/applicants/applicant.py	(revision 7347)
@@ -93,9 +93,5 @@
             return False, 'Registration Number exists.'
         # Has the course_admitted field been properly filled?
-        code = self.course_admitted.get('code')
-        if code:
-            catalog = getUtility(ICatalog, name='certificates_catalog')
-            cert = [i for i in catalog.searchResults(code=(code,code))][0]
-        else:
+        if self.course_admitted is None:
             return False, 'No course admitted provided.'
         # Add student object
@@ -117,5 +113,5 @@
         student.phone = self.phone
         # Save the certificate
-        student['studycourse'].certificate = cert
+        student['studycourse'].certificate = self.course_admitted
         # Create StudentApplication object ...
         application = createObject(u'waeup.StudentApplication')
Index: main/waeup.sirp/trunk/src/waeup/sirp/applicants/browser.py
===================================================================
--- main/waeup.sirp/trunk/src/waeup/sirp/applicants/browser.py	(revision 7346)
+++ main/waeup.sirp/trunk/src/waeup/sirp/applicants/browser.py	(revision 7347)
@@ -438,6 +438,5 @@
     grok.template('applicantdisplaypage')
     form_fields = grok.AutoFields(IApplicant).omit(
-        'locked',
-        'password')
+        'locked', 'course_admitted', 'password')
     form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
     label = 'Applicant'
@@ -469,13 +468,15 @@
             container_title, self.context.application_number)
 
-    def getDepartmentAdmitted(self):
-        course_admitted = getattr(self.context,'course_admitted',None)
-        if course_admitted:
-            return course_admitted['dep']
-
-    def getFacultyAdmitted(self):
-        course_admitted = getattr(self.context,'course_admitted',None)
-        if course_admitted:
-            return course_admitted['fac']
+    def getCourseAdmitted(self):
+        """Return link, title and code in html format to the certificate
+           admitted.
+        """
+        course_admitted = self.context.course_admitted
+        if ICertificate.providedBy(course_admitted):
+            url = self.url(course_admitted)
+            title = course_admitted.title
+            code = course_admitted.code
+            return '<a href="%s">%s - %s</a>' %(url,code,title)
+        return ''
 
 class ApplicantBaseDisplayFormPage(ApplicantDisplayFormPage):
@@ -672,6 +673,5 @@
     grok.require('waeup.viewApplication')
     form_fields = grok.AutoFields(IApplicant).omit(
-        'locked',
-        )
+        'locked', 'course_admitted')
     form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
     prefix = 'form'
@@ -683,14 +683,14 @@
             container_title, self.context.application_number)
 
-    def getDepartmentAdmitted(self):
-        course_admitted = getattr(self.context,'course_admitted',None)
-        if course_admitted:
-            return course_admitted['dep']
-
-    def getFacultyAdmitted(self):
-        course_admitted = getattr(self.context,'course_admitted',None)
-        if course_admitted:
-            return course_admitted['fac']
-
+    def getCourseAdmitted(self):
+        """Return title and code in html format to the certificate
+           admitted.
+        """
+        course_admitted = self.context.course_admitted
+        if ICertificate.providedBy(course_admitted):
+            title = course_admitted.title
+            code = course_admitted.code
+            return '%s - %s' %(code,title)
+        return ''
 
     def setUpWidgets(self, ignore_request=False):
@@ -755,21 +755,26 @@
             f_text = Paragraph(f_text, style["Normal"])
             data.append([f_label,f_text])
-        #f_label = '<font size=12>Admitted Course of Study:</font>'
-        #f_text = '<font size=12>%s</font>' % self.getCourseAdmitted()
-        #f_label = Paragraph(f_label, style["Normal"])
-        #f_text = Paragraph(f_text, style["Normal"])
-        #data.append([f_label,f_text])
-
-        f_label = '<font size=12>Department:</font>'
-        f_text = '<font size=12>%s</font>' % self.getDepartmentAdmitted()
+        course_admitted = self.getCourseAdmitted()
+        f_label = '<font size=12>Admitted Course of Study:</font>'
+        f_text = '<font size=12>%s</font>' % course_admitted
         f_label = Paragraph(f_label, style["Normal"])
         f_text = Paragraph(f_text, style["Normal"])
         data.append([f_label,f_text])
 
-        f_label = '<font size=12>Faculty:</font>'
-        f_text = '<font size=12>%s</font>' % self.getFacultyAdmitted()
-        f_label = Paragraph(f_label, style["Normal"])
-        f_text = Paragraph(f_text, style["Normal"])
-        data.append([f_label,f_text])
+        course_admitted = self.context.course_admitted
+        if ICertificate.providedBy(course_admitted):
+            f_label = '<font size=12>Department:</font>'
+            f_text = '<font size=12>%s</font>' % (
+                course_admitted.__parent__.__parent__.longtitle())
+            f_label = Paragraph(f_label, style["Normal"])
+            f_text = Paragraph(f_text, style["Normal"])
+            data.append([f_label,f_text])
+
+            f_label = '<font size=12>Faculty:</font>'
+            f_text = '<font size=12>%s</font>' % (
+                course_admitted.__parent__.__parent__.__parent__.longtitle())
+            f_label = Paragraph(f_label, style["Normal"])
+            f_text = Paragraph(f_text, style["Normal"])
+            data.append([f_label,f_text])
 
         # Create table
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 7346)
+++ main/waeup.sirp/trunk/src/waeup/sirp/applicants/browser_templates/applicantdisplaypage.pt	(revision 7347)
@@ -27,16 +27,8 @@
     <tr>
       <td class="fieldname">
-          Department:
+          Admitted Course of Study:
       </td>
       <td>
-        <span tal:replace="view/getDepartmentAdmitted" />
-      </td>
-    </tr>
-    <tr>
-      <td class="fieldname">
-          Faculty:
-      </td>
-      <td>
-        <span tal:replace="view/getFacultyAdmitted" />
+        <span tal:replace="structure view/getCourseAdmitted" />
       </td>
     </tr>
Index: main/waeup.sirp/trunk/src/waeup/sirp/applicants/dynamicroles.py
===================================================================
--- main/waeup.sirp/trunk/src/waeup/sirp/applicants/dynamicroles.py	(revision 7347)
+++ main/waeup.sirp/trunk/src/waeup/sirp/applicants/dynamicroles.py	(revision 7347)
@@ -0,0 +1,46 @@
+## $Id$
+##
+## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## 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
+##
+"""Security policy components for applicants.
+
+Applicants need special security policy treatment, as officers with
+local roles for departments and faculties might have additional
+permissions (local roles on depts/faculties) here.
+"""
+import grok
+from zope.securitypolicy.interfaces import IPrincipalRoleManager
+from waeup.sirp.applicants.interfaces import IApplicant
+from waeup.sirp.students.dynamicroles import StudentPrincipalRoleManager
+
+# All components in here have the same context: Applicant instances
+grok.context(IApplicant)
+
+class ApplicantPrincipalRoleManager(StudentPrincipalRoleManager):
+
+    grok.provides(IPrincipalRoleManager)
+
+    #: The attribute name to lookup for additional roles
+    extra_attrib = 'course1'
+    subcontainer = None
+
+    # Role name mapping:
+    # role name to look for in `extra_attrib` and parents
+    # to
+    # role to add in case this role was found
+    rolename_mapping = {
+        'waeup.local.ClearanceOfficer':'waeup.ApplicationsOfficer',
+        }
Index: main/waeup.sirp/trunk/src/waeup/sirp/applicants/interfaces.py
===================================================================
--- main/waeup.sirp/trunk/src/waeup/sirp/applicants/interfaces.py	(revision 7346)
+++ main/waeup.sirp/trunk/src/waeup/sirp/applicants/interfaces.py	(revision 7347)
@@ -32,8 +32,10 @@
     ISIRPObject, year_range, validate_email, academic_sessions_vocab)
 from waeup.sirp.university.vocabularies import application_categories
-from waeup.sirp.students.vocabularies import lgas_vocab, GenderSource
+from waeup.sirp.students.vocabularies import (
+  lgas_vocab, CertificateSource, GenderSource,
+  )
 from waeup.sirp.applicants.vocabularies import (
   application_types_vocab, application_pins_vocab,
-  AppCatCertificateTitleSource, CertificateTitleSource
+  AppCatCertificateSource,
   )
 from waeup.sirp.payments.interfaces import IOnlinePayment
@@ -343,10 +345,10 @@
     course1 = schema.Choice(
         title = u'1st Choice Course of Study',
-        source = CertificateTitleSource(),
+        source = CertificateSource(),
         required = True,
         )
     course2 = schema.Choice(
         title = u'2nd Choice Course of Study',
-        source = CertificateTitleSource(),
+        source = CertificateSource(),
         required = False,
         )
@@ -363,12 +365,12 @@
         required = False,
         )
+    course_admitted = schema.Choice(
+        title = u'Admitted Course of Study',
+        source = CertificateSource(),
+        default = None,
+        required = False,
+        )
     notice = schema.Text(
         title = u'Notice',
-        required = False,
-        )
-    course_admitted = schema.Choice(
-        title = u'Admitted Course of Study',
-        source = CertificateTitleSource(),
-        default = None,
         required = False,
         )
@@ -423,10 +425,10 @@
     course1 = schema.Choice(
         title = u'1st Choice Course of Study',
-        source = AppCatCertificateTitleSource(),
+        source = AppCatCertificateSource(),
         required = True,
         )
     course2 = schema.Choice(
         title = u'2nd Choice Course of Study',
-        source = AppCatCertificateTitleSource(),
+        source = AppCatCertificateSource(),
         required = False,
         )
@@ -443,5 +445,5 @@
     course_admitted = schema.Choice(
         title = u'Admitted Course of Study',
-        source = CertificateTitleSource(),
+        source = CertificateSource(),
         default = None,
         required = False,
Index: main/waeup.sirp/trunk/src/waeup/sirp/applicants/tests/test_dynamicroles.py
===================================================================
--- main/waeup.sirp/trunk/src/waeup/sirp/applicants/tests/test_dynamicroles.py	(revision 7347)
+++ main/waeup.sirp/trunk/src/waeup/sirp/applicants/tests/test_dynamicroles.py	(revision 7347)
@@ -0,0 +1,85 @@
+## $Id$
+##
+## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## 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
+##
+"""
+Tests for dynamic roles concerning applicants and related.
+"""
+from zope.interface import verify
+from zope.securitypolicy.interfaces import IPrincipalRoleManager
+from zope.securitypolicy.settings import Allow
+from zope.securitypolicy.tests.test_annotationprincipalrolemanager import (
+    Test as APRMTest, Manageable)
+from waeup.sirp.testing import FunctionalLayer
+from waeup.sirp.applicants.tests.test_browser import ApplicantsFullSetup
+from waeup.sirp.applicants import ApplicantPrincipalRoleManager
+
+class ApplicantPrincipalRoleManagerTests(APRMTest):
+    # Make sure our PRM behaves like a regular one for usual cases.
+    # Usual cases are ones, that do not interact with external officers.
+    # See next test case for functional tests including officer perms.
+
+    def _make_roleManager(self, obj=None):
+        # Overriding this method of original testcase we make sure
+        # that really an Applicant PRM is tested.
+        if obj is None:
+            obj = Manageable()
+        return ApplicantPrincipalRoleManager(obj)
+
+class ApplicantPrincipalRoleManagerFunctionalTests(ApplicantsFullSetup):
+
+    layer = FunctionalLayer
+
+    def setUp(self):
+        super(ApplicantPrincipalRoleManagerFunctionalTests, self).setUp()
+        self.applicant.course1 = self.certificate
+        self.officer_role = 'waeup.ApplicationsOfficer'
+        # assign clearance permissions for a virtual officer
+        prm = IPrincipalRoleManager(self.department)
+        prm.assignRoleToPrincipal('waeup.local.ClearanceOfficer', 'alice')
+        return
+
+    def test_iface(self):
+        # make sure out ApplicantPRM really implements required ifaces.
+        obj = ApplicantPrincipalRoleManager(self.applicant)
+        verify.verifyClass(IPrincipalRoleManager,
+                           ApplicantPrincipalRoleManager)
+        verify.verifyObject(IPrincipalRoleManager, obj)
+        return
+
+    def test_get_as_adapter(self):
+        # we can get an ApplicantPRM for Applicants by adapter lookup
+        prm = IPrincipalRoleManager(self.applicant)
+        self.assertTrue(
+            isinstance(prm, ApplicantPrincipalRoleManager))
+        return
+
+    def test_no_officer_set(self):
+        # if the faculty/dept. of the connected cert has no local
+        # roles set, we won't get any additional roles for our
+        # applicant
+        prm = IPrincipalRoleManager(self.applicant)
+        result = prm.getRolesForPrincipal('bob')
+        self.assertEqual(result, [])
+        return
+
+    def test_valid_officer(self):
+        # for an officer that has clearance role on the connected dept
+        # we get the ApplicationsOfficer role on our applicant
+        prm = IPrincipalRoleManager(self.applicant)
+        result = prm.getRolesForPrincipal('alice')
+        self.assertEqual(result, [('waeup.ApplicationsOfficer', Allow)])
+        return
Index: main/waeup.sirp/trunk/src/waeup/sirp/applicants/vocabularies.py
===================================================================
--- main/waeup.sirp/trunk/src/waeup/sirp/applicants/vocabularies.py	(revision 7346)
+++ main/waeup.sirp/trunk/src/waeup/sirp/applicants/vocabularies.py	(revision 7347)
@@ -21,5 +21,5 @@
 from zope.catalog.interfaces import ICatalog
 from waeup.sirp.interfaces import SimpleSIRPVocabulary
-from zc.sourcefactory.contextual import BasicContextualSourceFactory
+from waeup.sirp.students.vocabularies import CertificateSource
 
 #: Types of applications we support.
@@ -44,27 +44,7 @@
     *[(u"%s (%s)" % (x[2],x[0]),x[2]) for x in APPLICATION_TYPES])
 
-class CertificateTitleSource(BasicContextualSourceFactory):
-    """A certificate title source delivers all certificates titles provided
-    in the portal.
-    """
-    def getValues(self, context):
-        catalog = getUtility(ICatalog, name='certificates_catalog')
-        certs = list(catalog.searchResults(code=('', 'z*')))
-        certs = [dict(
-            code = i.code,
-            title = i.title,
-            dep = i.__parent__.__parent__.longtitle(),
-            fac = i.__parent__.__parent__.__parent__.longtitle()
-            ) for i in certs]
-        return sorted(certs, key=lambda value: value['code'])
 
-    def getToken(self, context, value):
-        return value['code']
-
-    def getTitle(self, context, value):
-        return "%s - %s" % (value['code'], value['title'][:64])
-
-class AppCatCertificateTitleSource(CertificateTitleSource):
-    """An application certificate source delivers all course titles which belong to
+class AppCatCertificateSource(CertificateSource):
+    """An application certificate source delivers all courses which belong to
     a certain application_category.
     """
@@ -72,13 +52,7 @@
         appcat = context.__parent__.application_category
         catalog = getUtility(ICatalog, name='certificates_catalog')
-        certs = list(catalog.searchResults(code=('', 'z*'),
-            application_category=(appcat,appcat)))
-        certs = [dict(
-            code = i.code,
-            title = i.title,
-            dep = i.__parent__.__parent__.longtitle(),
-            fac = i.__parent__.__parent__.__parent__.longtitle()
-            ) for i in certs]
-        return sorted(certs, key=lambda value: value['code'])
-
-
+        return sorted(list(
+                catalog.searchResults(
+                    code=('', 'z*'),
+                    application_category=(appcat,appcat))),
+                    key=lambda value: value.code)
