Index: main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/api.txt
===================================================================
--- main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/api.txt	(revision 9209)
+++ main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/api.txt	(revision 9211)
@@ -28,3 +28,3 @@
    :maxdepth: 1
 
-   certcourses.txt
+   courserefs.txt
Index: main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/batching.py
===================================================================
--- main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/batching.py	(revision 9209)
+++ main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/batching.py	(revision 9211)
@@ -29,15 +29,8 @@
 from zope.schema import getFields
 from zope.catalog.interfaces import ICatalog
-from zope.event import notify
-from zope.securitypolicy.interfaces import (
-    IPrincipalRoleManager, IPrincipalRoleMap)
-from waeup.kofa.authentication import LocalRoleSetEvent
-from waeup.kofa.interfaces import (
-    IBatchProcessor, IGNORE_MARKER, DELETION_MARKER, FatalCSVError)
+from waeup.kofa.interfaces import IBatchProcessor, IGNORE_MARKER, FatalCSVError
 from waeup.kofa.university.interfaces import (
     IFacultiesContainer, IFaculty, ICourse, IDepartment, ICertificate,
     ICertificateCourse)
-from waeup.kofa.university import (
-    Faculty, Department, Course, Certificate)
 from waeup.kofa.utils.batching import BatchProcessor
 
@@ -53,5 +46,4 @@
     name = u'Faculty Processor'
     iface = IFaculty
-    allowed_roles = Faculty.local_roles
 
     location_fields = ['code',]
@@ -62,4 +54,141 @@
     def parentsExist(self, row, site):
         return 'faculties' in site.keys()
+
+    def entryExists(self, row, site):
+        return row['code'] in site['faculties'].keys()
+
+    def getParent(self, row, site):
+        return site['faculties']
+
+    def getEntry(self, row, site):
+        if not self.entryExists(row, site):
+            return None
+        parent = self.getParent(row, site)
+        return parent.get(row['code'])
+
+    def addEntry(self, obj, row, site):
+        parent = self.getParent(row, site)
+        parent.addFaculty(obj)
+        return
+
+    def delEntry(self, row, site):
+        parent = self.getParent(row, site)
+        del parent[row['code']]
+        pass
+
+class DepartmentProcessor(BatchProcessor):
+    """A batch processor for IDepartment objects.
+    """
+    grok.implements(IBatchProcessor)
+    grok.provides(IBatchProcessor)
+    grok.context(Interface)
+    util_name = 'departmentprocessor'
+    grok.name(util_name)
+
+    name = u'Department Processor'
+    iface = IDepartment
+
+    location_fields = ['code', 'faculty_code']
+    factory_name = 'waeup.Department'
+
+    mode = None
+
+    def parentsExist(self, row, site):
+        if not 'faculties' in site.keys():
+            return False
+        return row['faculty_code'] in site['faculties']
+
+    def entryExists(self, row, site):
+        if not self.parentsExist(row, site):
+            return False
+        parent = self.getParent(row, site)
+        return row['code'] in parent.keys()
+
+    def getParent(self, row, site):
+        return site['faculties'][row['faculty_code']]
+
+    def getEntry(self, row, site):
+        if not self.entryExists(row, site):
+            return None
+        parent = self.getParent(row, site)
+        return parent.get(row['code'])
+
+    def addEntry(self, obj, row, site):
+        parent = self.getParent(row, site)
+        parent.addDepartment(obj)
+        return
+
+    def delEntry(self, row, site):
+        parent = self.getParent(row, site)
+        del parent[row['code']]
+        return
+
+class CourseProcessor(BatchProcessor):
+    """A batch processor for ICourse objects.
+    """
+    grok.implements(IBatchProcessor)
+    grok.provides(IBatchProcessor)
+    grok.context(Interface)
+    util_name = 'courseprocessor'
+    grok.name(util_name)
+
+    name = u'Course Processor'
+    iface = ICourse
+
+    location_fields = ['code', 'faculty_code', 'department_code']
+    factory_name = 'waeup.Course'
+
+    mode = None
+
+    def parentsExist(self, row, site):
+        if not 'faculties' in site.keys():
+            return False
+        if not row['faculty_code'] in site['faculties'].keys():
+            return False
+        faculty = site['faculties'][row['faculty_code']]
+        return row['department_code'] in faculty.keys()
+
+    def entryExists(self, row, site):
+        if not self.parentsExist(row, site):
+            return False
+        parent = self.getParent(row, site)
+        return row['code'] in parent.keys()
+
+    def getParent(self, row, site):
+        dept = site['faculties'][row['faculty_code']][row['department_code']]
+        return dept.courses
+
+    def getEntry(self, row, site):
+        if not self.entryExists(row, site):
+            return None
+        parent = self.getParent(row, site)
+        return parent.get(row['code'])
+
+    def addEntry(self, obj, row, site):
+        parent = self.getParent(row, site)
+        parent.addCourse(obj)
+        return
+
+    def delEntry(self, row, site):
+        parent = self.getParent(row, site)
+        del parent[row['code']]
+        return
+
+class CertificateProcessor(BatchProcessor):
+    """A batch processor for ICertificate objects.
+    """
+    grok.implements(IBatchProcessor)
+    grok.provides(IBatchProcessor)
+    grok.context(Interface)
+    util_name = 'certificateprocessor'
+    grok.name(util_name)
+
+    name = u'Certificate Processor'
+    iface = ICertificate
+
+    location_fields = ['code']
+    factory_name = 'waeup.Certificate'
+
+    mode = None
 
     @property
@@ -67,224 +196,5 @@
         fields = getFields(self.iface)
         return sorted(list(set(
-            self.location_fields + fields.keys() + ['local_roles']
-            )))
-
-    def entryExists(self, row, site):
-        return row['code'] in site['faculties'].keys()
-
-    def getParent(self, row, site):
-        return site['faculties']
-
-    def getEntry(self, row, site):
-        if not self.entryExists(row, site):
-            return None
-        parent = self.getParent(row, site)
-        return parent.get(row['code'])
-
-    def addEntry(self, obj, row, site):
-        parent = self.getParent(row, site)
-        parent.addFaculty(obj)
-        return
-
-    def delEntry(self, row, site):
-        parent = self.getParent(row, site)
-        del parent[row['code']]
-        pass
-
-    def updateEntry(self, obj, row, site):
-        """Update obj to the values given in row.
-        """
-        items_changed = ''
-
-        if row.has_key('local_roles') and row['local_roles'] not in (
-            None, IGNORE_MARKER):
-            role_manager = IPrincipalRoleManager(obj)
-            role_map = IPrincipalRoleMap(obj)
-            # Remove all existing local roles.
-            for local_role, user_name, setting in role_map.getPrincipalsAndRoles():
-                role_manager.unsetRoleForPrincipal(local_role, user_name)
-                notify(LocalRoleSetEvent(
-                        obj, local_role, user_name, granted=False))
-            # Add new local roles.
-            if row['local_roles'] != DELETION_MARKER:
-                local_roles = eval(row['local_roles'])
-                for rolemap in local_roles:
-                    user = rolemap['user_name']
-                    local_role = rolemap['local_role']
-                    role_manager.assignRoleToPrincipal(local_role, user)
-                    notify(LocalRoleSetEvent(obj, local_role, user, granted=True))
-                    items_changed += (
-                        '%s=%s, ' % ('local_roles', '%s|%s' % (user,local_role)))
-            row.pop('local_roles')
-
-        # apply other values...
-        items_changed += super(FacultyProcessor, self).updateEntry(
-            obj, row, site)
-
-        # Log actions...
-        location_field = self.location_fields[0]
-        grok.getSite().logger.info('%s - %s - Record updated: %s'
-            % (self.name, row[location_field], items_changed))
-        return items_changed
-
-    def checkConversion(self, row, mode='create'):
-        """Validates all values in row.
-        """
-        errs, inv_errs, conv_dict =  super(
-            FacultyProcessor, self).checkConversion(row, mode=mode)
-        if row.has_key('local_roles'):
-            if row['local_roles'] in (None, DELETION_MARKER, IGNORE_MARKER):
-                return errs, inv_errs, conv_dict
-            try:
-                local_roles = eval(row['local_roles'])
-            except:
-                errs.append(('local_roles','Error'))
-                return errs, inv_errs, conv_dict
-            if not isinstance(local_roles, list):
-                errs.append(('local_roles','no list'))
-                return errs, inv_errs, conv_dict
-            for rolemap in local_roles:
-                if not isinstance(rolemap, dict):
-                    errs.append(('local_roles','no dicts'))
-                    return errs, inv_errs, conv_dict
-                if not 'user_name' in rolemap.keys() or not \
-                    'local_role' in rolemap.keys():
-                    errs.append(('local_roles','user_name or local_role missing'))
-                    return errs, inv_errs, conv_dict
-                local_role = rolemap['local_role']
-                if not local_role in self.allowed_roles:
-                    errs.append(('local_roles','%s not allowed' % local_role))
-                    return errs, inv_errs, conv_dict
-                user = rolemap['user_name']
-                users = grok.getSite()['users']
-                if not user in users.keys():
-                    errs.append(('local_roles','%s does not exist' % user))
-                    return errs, inv_errs, conv_dict
-        return errs, inv_errs, conv_dict
-
-class DepartmentProcessor(FacultyProcessor):
-    """A batch processor for IDepartment objects.
-    """
-    grok.implements(IBatchProcessor)
-    grok.provides(IBatchProcessor)
-    grok.context(Interface)
-    util_name = 'departmentprocessor'
-    grok.name(util_name)
-
-    name = u'Department Processor'
-    iface = IDepartment
-    allowed_roles = Department.local_roles
-
-    location_fields = ['code', 'faculty_code']
-    factory_name = 'waeup.Department'
-
-    mode = None
-
-    def parentsExist(self, row, site):
-        if not 'faculties' in site.keys():
-            return False
-        return row['faculty_code'] in site['faculties']
-
-    def entryExists(self, row, site):
-        if not self.parentsExist(row, site):
-            return False
-        parent = self.getParent(row, site)
-        return row['code'] in parent.keys()
-
-    def getParent(self, row, site):
-        return site['faculties'][row['faculty_code']]
-
-    def getEntry(self, row, site):
-        if not self.entryExists(row, site):
-            return None
-        parent = self.getParent(row, site)
-        return parent.get(row['code'])
-
-    def addEntry(self, obj, row, site):
-        parent = self.getParent(row, site)
-        parent.addDepartment(obj)
-        return
-
-    def delEntry(self, row, site):
-        parent = self.getParent(row, site)
-        del parent[row['code']]
-        return
-
-class CourseProcessor(FacultyProcessor):
-    """A batch processor for ICourse objects.
-    """
-    grok.implements(IBatchProcessor)
-    grok.provides(IBatchProcessor)
-    grok.context(Interface)
-    util_name = 'courseprocessor'
-    grok.name(util_name)
-
-    name = u'Course Processor'
-    iface = ICourse
-    allowed_roles = Course.local_roles
-
-    location_fields = ['code', 'faculty_code', 'department_code']
-    factory_name = 'waeup.Course'
-
-    mode = None
-
-    def parentsExist(self, row, site):
-        if not 'faculties' in site.keys():
-            return False
-        if not row['faculty_code'] in site['faculties'].keys():
-            return False
-        faculty = site['faculties'][row['faculty_code']]
-        return row['department_code'] in faculty.keys()
-
-    def entryExists(self, row, site):
-        if not self.parentsExist(row, site):
-            return False
-        parent = self.getParent(row, site)
-        return row['code'] in parent.keys()
-
-    def getParent(self, row, site):
-        dept = site['faculties'][row['faculty_code']][row['department_code']]
-        return dept.courses
-
-    def getEntry(self, row, site):
-        if not self.entryExists(row, site):
-            return None
-        parent = self.getParent(row, site)
-        return parent.get(row['code'])
-
-    def addEntry(self, obj, row, site):
-        parent = self.getParent(row, site)
-        parent.addCourse(obj)
-        return
-
-    def delEntry(self, row, site):
-        parent = self.getParent(row, site)
-        del parent[row['code']]
-        return
-
-class CertificateProcessor(FacultyProcessor):
-    """A batch processor for ICertificate objects.
-    """
-    grok.implements(IBatchProcessor)
-    grok.provides(IBatchProcessor)
-    grok.context(Interface)
-    util_name = 'certificateprocessor'
-    grok.name(util_name)
-
-    name = u'Certificate Processor'
-    iface = ICertificate
-    allowed_roles = Certificate.local_roles
-
-    location_fields = ['code']
-    factory_name = 'waeup.Certificate'
-
-    mode = None
-
-    @property
-    def available_fields(self):
-        fields = getFields(self.iface)
-        return sorted(list(set(
-            ['faculty_code','department_code'] + fields.keys()
-            + ['local_roles'])))
+            ['faculty_code','department_code'] + fields.keys())))
 
     def checkHeaders(self, headerfields, mode='create'):
@@ -359,5 +269,5 @@
         return
 
-class CertificateCourseProcessor(FacultyProcessor):
+class CertificateCourseProcessor(BatchProcessor):
     """A batch processor for ICertificateCourse objects.
     """
@@ -407,5 +317,5 @@
     def addEntry(self, obj, row, site):
         parent = self.getParent(row, site)
-        parent.addCertCourse(row['course'],
+        parent.addCourseRef(row['course'],
                             row['level'], row['mandatory'])
         return
@@ -413,4 +323,4 @@
     def delEntry(self, row, site):
         parent = self.getParent(row, site)
-        parent.delCertCourse(row['course'].code, row['level'])
-        return
+        parent.delCourseRef(row['course'].code, row['level'])
+        return
Index: main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/catalog.py
===================================================================
--- main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/catalog.py	(revision 9209)
+++ main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/catalog.py	(revision 9211)
@@ -159,5 +159,5 @@
         self.title = context.course.title
         self.code = context.getCourseCode
-        self.type = 'Certificate Course'
+        self.type = 'Course Referrer'
 
 def search(query=None, view=None):
Index: main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/certcourses.txt
===================================================================
--- main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/certcourses.txt	(revision 9209)
+++ 	(revision )
@@ -1,116 +1,0 @@
-Certificate Courses
-*******************
-
-Courses are referred to by certificate courses.
-
-We can be sure, that when removing courses or referrers of them,
-other data will be handled in a way we expect. That is,
-
-* when we remove a course, we want all referrers to it be removed as
-  well
-
-* when we remove a referrer of a course, nothing else should be
-  touched but only this very referrer.
-
-
-As correct deletion of referrer needs support of catalogs (we lookup
-catalogs to find referrers), we need a fully blown ZODB here,
-including an instance of :class:`waeup.kofa.app.University`, as this is our
-`ISite` object where the catalogs are stored.
-
-We create a complete university tree, starting with a
-:class:`University` instance and going down to courses, certificats
-and course certificates:
-
-    >>> from waeup.kofa.app import University
-    >>> root = getRootFolder()
-    >>> root['app'] = University()
-
-We have to set the site, to enable proper working of catalogs and
-`IObjectRemoved` handlers:
-
-    >>> from zope.app.component.hooks import setSite
-    >>> setSite(root['app'])
-
-    >>> from zope.component import createObject
-    >>> faculty = createObject('waeup.Faculty')
-    >>> faculty.code = 'FAC'
-    >>> root['app']['faculties'].addFaculty(faculty)
-    >>> fac = root['app']['faculties']['FAC']
-
-    >>> dept = createObject('waeup.Department')
-    >>> dept.code = 'DEPT'
-    >>> root['app']['faculties']['FAC'].addDepartment(dept)
-
-    >>> course = createObject('waeup.Course')
-    >>> course.code = 'CRS1'
-    >>> fac['DEPT'].courses.addCourse(course)
-
-Now let's add another department, which will also hold a course:
-
-    >>> dept2 = createObject('waeup.Department')
-    >>> dept2.code = 'DEPT2'
-    >>> root['app']['faculties']['FAC'].addDepartment(dept2)
-
-    >>> course = createObject('waeup.Course')
-    >>> course.code = 'CRS2'
-    >>> fac['DEPT2'].courses.addCourse(course)
-
-Finally, we add a certificate with referrers (certificate courses) to
-the created courses:
-
-    >>> cert = createObject('waeup.Certificate')
-    >>> cert.code = 'CERT'
-    >>> fac['DEPT'].certificates.addCertificate(cert)
-
-    >>> fac['DEPT'].certificates['CERT'].addCertCourse(
-    ...   fac['DEPT'].courses['CRS1'])
-
-    >>> fac['DEPT'].certificates['CERT'].addCertCourse(
-    ...   fac['DEPT2'].courses['CRS2'])
-
-We add the latter course again, but this time for the second level:
-
-    >>> fac['DEPT'].certificates['CERT'].addCertCourse(
-    ...   fac['DEPT2'].courses['CRS2'], level=200)
-
-So, we have three referrers in our certificate of two different
-courses stored somewhere in the application:
-
-    >>> list(fac['DEPT'].certificates['CERT'])
-    [u'CRS1_100', u'CRS2_100', u'CRS2_200']
-
-Both, the courses themselves and the referrers of them, are the same
-(not only equal):
-
-    >>> a = root['app']['faculties']['FAC']['DEPT']
-    >>> b1 = a.courses['CRS1']
-    >>> b2 = a.certificates['CERT']['CRS1_100'].course
-    >>> b1 is b2
-    True
-
-If we remove the course, also the select item in the certificate course
-will be gone:
-
-    >>> list(a.certificates['CERT'])
-    [u'CRS1_100', u'CRS2_100', u'CRS2_200']
-
-    >>> list(a.courses)
-    [u'CRS1']
-
-    >>> del root['app']['faculties']['FAC']['DEPT'].courses['CRS1']
-    >>> list(a.certificates['CERT'])
-    [u'CRS2_100', u'CRS2_200']
-
-    >>> list(a.courses)
-    []
-
-If we remove a referrer of a course, this will not touch the course
-nor other referrers:
-
-    >>> del fac['DEPT'].certificates['CERT']['CRS2_200']
-    >>> list(a.certificates['CERT'])
-    [u'CRS2_100']
-
-    >>> list(fac['DEPT2'].courses)
-    [u'CRS2']
Index: main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/certificate.py
===================================================================
--- main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/certificate.py	(revision 9209)
+++ main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/certificate.py	(revision 9211)
@@ -36,18 +36,18 @@
     grok.implements(ICertificate, ICertificateAdd)
 
-    local_roles = [
-        'waeup.local.CourseAdviser100',
-        'waeup.local.CourseAdviser200',
-        'waeup.local.CourseAdviser300',
-        'waeup.local.CourseAdviser400',
-        'waeup.local.CourseAdviser500',
-        'waeup.local.CourseAdviser600',
-        ]
+    @property       # Make this method read_only and looking like an attr.
+    def local_roles(self):
+        return ['waeup.local.CourseAdviser100',
+                'waeup.local.CourseAdviser200',
+                'waeup.local.CourseAdviser300',
+                'waeup.local.CourseAdviser400',
+                'waeup.local.CourseAdviser500',
+                'waeup.local.CourseAdviser600',
+                ]
 
     def __init__(self, code=u'NA', title=u'Unnamed Certificate',
                  study_mode=None, start_level=None,
                  end_level=None, application_category=None,
-                 school_fee_1=None, school_fee_2=None,
-                 school_fee_3=None, school_fee_4=None):
+                 school_fee_1=None, school_fee_2=None):
         super(Certificate, self).__init__()
         self.code = code
@@ -59,12 +59,10 @@
         self.school_fee_1 = school_fee_1
         self.school_fee_2 = school_fee_2
-        self.school_fee_3 = school_fee_3
-        self.school_fee_4 = school_fee_4
 
     def longtitle(self):
         return "%s (%s)" % (self.title,self.code)
 
-    def addCertCourse(self, course, level=100, mandatory=True):
-        """Add a certificate course.
+    def addCourseRef(self, course, level=100, mandatory=True):
+        """Add a course referrer.
         """
         code = "%s_%s" % (course.code, level)
@@ -74,6 +72,6 @@
         self._p_changed = True
 
-    def delCertCourse(self, code, level=None):
-        """Delete a certificate course denoted by its code.
+    def delCourseRef(self, code, level=None):
+        """Delete a course referrer denoted by its code.
         """
         keys = list(self.keys()) # create list copy
Index: main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/certificate.txt
===================================================================
--- main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/certificate.txt	(revision 9209)
+++ main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/certificate.txt	(revision 9211)
@@ -96,19 +96,19 @@
       .. XXX: This is not a proper description
 
-   .. method:: addCertCourse(course[, level=100,[ mandatory=True]])
-
-      Add a certificate course. A course is an object implementing
+   .. method:: addCourseRef(course[, level=100,[ mandatory=True]])
+
+      Add a referrer of a course. A course is an object implementing
       :class:`waeup.kofa.interfaces.ICourse`.
 
-      Please don't be confused by the term 'certificate course'.
-      As course objects will normaly be  stored
-      in a department, the certificate course refers to
+      Please don't be confused by the term 'referrer'. As course objects 
+      will normaly be  stored
+      in a department, the course referrer refers to 
       the 'real' course in the department container.
 
-   .. method:: delCertCourse(code)
-
-      Remove a certificate course from a certificate.
-
-      The certificate course must be given by its code number.
+   .. method:: delCourseRef(code)
+
+      Remove a course referrer from a certificate.
+
+      The referrer must be given by its code number.
 
 :class:`CertificateCourse`
@@ -117,12 +117,12 @@
 .. class:: CertificateCourse(course[, level=100[, mandatory=True]])
 
-   Create a certificate course.
-
-   A certificate course are held in
+   Create a course referrer.
+
+   A certificate course (old term) or course referrer are held in
    certificates and refer to an existing :class:`Course` instance held
    elsewhere.
 
-   A certificate can require several certificate courses and one
-   certificate course can be required by several certificates.
+   A certificate can require several course referrers and one
+   course referrer can be required by several certificates.
 
    .. attribute:: course
@@ -225,5 +225,5 @@
 
    To accomplish that, the parents of the removed course are looked up
-   for a certifcate container which contains a certificate course that
+   for a certifcate container which contains a course referrer that
    refers to the deleted course.
 
@@ -341,5 +341,5 @@
     >>> mycert.code = 'MYCERT'
     >>> mydept.certificates.addCertificate(mycert)
-    >>> mycert.addCertCourse(mycourse)
+    >>> mycert.addCourseRef(mycourse)
 
     >>> transaction.commit()
@@ -354,5 +354,5 @@
     ['mydept']
 
-The certificate course we stored in the certificate is indeed a
+The course referrer we stored in the certificate is indeed a
 referrer of the course, not a copy of it:
 
Index: main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/course.py
===================================================================
--- main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/course.py	(revision 9209)
+++ main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/course.py	(revision 9211)
@@ -29,6 +29,4 @@
     """
     grok.implements(ICourse, ICourseAdd)
-
-    local_roles = ['waeup.local.Lecturer']
 
     def __init__(self,
@@ -80,5 +78,5 @@
         # Remove that referrer...
         cert = certcourse.__parent__
-        cert.delCertCourse(code)
+        cert.delCourseRef(code)
         cert._p_changed = True
     return
Index: main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/courserefs.txt
===================================================================
--- main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/courserefs.txt	(revision 9211)
+++ main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/courserefs.txt	(revision 9211)
@@ -0,0 +1,116 @@
+Course referrers
+****************
+
+Courses are referred to by certificate courses.
+
+We can be sure, that when removing courses or referrers of them,
+other data will be handled in a way we expect. That is,
+
+* when we remove a course, we want all referrers to it be removed as
+  well
+
+* when we remove a referrer of a course, nothing else should be
+  touched but only this very referrer.
+
+
+As correct deletion of referrer needs support of catalogs (we lookup
+catalogs to find referrers), we need a fully blown ZODB here,
+including an instance of :class:`waeup.kofa.app.University`, as this is our
+`ISite` object where the catalogs are stored.
+
+We create a complete university tree, starting with a
+:class:`University` instance and going down to courses, certificats
+and course certificates:
+
+    >>> from waeup.kofa.app import University
+    >>> root = getRootFolder()
+    >>> root['app'] = University()
+
+We have to set the site, to enable proper working of catalogs and
+`IObjectRemoved` handlers:
+
+    >>> from zope.app.component.hooks import setSite
+    >>> setSite(root['app'])
+
+    >>> from zope.component import createObject
+    >>> faculty = createObject('waeup.Faculty')
+    >>> faculty.code = 'FAC'
+    >>> root['app']['faculties'].addFaculty(faculty)
+    >>> fac = root['app']['faculties']['FAC']
+
+    >>> dept = createObject('waeup.Department')
+    >>> dept.code = 'DEPT'
+    >>> root['app']['faculties']['FAC'].addDepartment(dept)
+
+    >>> course = createObject('waeup.Course')
+    >>> course.code = 'CRS1'
+    >>> fac['DEPT'].courses.addCourse(course)
+
+Now let's add another department, which will also hold a course:
+
+    >>> dept2 = createObject('waeup.Department')
+    >>> dept2.code = 'DEPT2'
+    >>> root['app']['faculties']['FAC'].addDepartment(dept2)
+
+    >>> course = createObject('waeup.Course')
+    >>> course.code = 'CRS2'
+    >>> fac['DEPT2'].courses.addCourse(course)
+
+Finally, we add a certificate with referrers (certificate courses) to
+the created courses:
+
+    >>> cert = createObject('waeup.Certificate')
+    >>> cert.code = 'CERT'
+    >>> fac['DEPT'].certificates.addCertificate(cert)
+
+    >>> fac['DEPT'].certificates['CERT'].addCourseRef(
+    ...   fac['DEPT'].courses['CRS1'])
+
+    >>> fac['DEPT'].certificates['CERT'].addCourseRef(
+    ...   fac['DEPT2'].courses['CRS2'])
+
+We add the latter course again, but this time for the second level:
+
+    >>> fac['DEPT'].certificates['CERT'].addCourseRef(
+    ...   fac['DEPT2'].courses['CRS2'], level=200)
+
+So, we have three referrers in our certificate of two different
+courses stored somewhere in the application:
+
+    >>> list(fac['DEPT'].certificates['CERT'])
+    [u'CRS1_100', u'CRS2_100', u'CRS2_200']
+
+Both, the courses themselves and the referrers of them, are the same
+(not only equal):
+
+    >>> a = root['app']['faculties']['FAC']['DEPT']
+    >>> b1 = a.courses['CRS1']
+    >>> b2 = a.certificates['CERT']['CRS1_100'].course
+    >>> b1 is b2
+    True
+
+If we remove the course, also the select item in the certificate course
+will be gone:
+
+    >>> list(a.certificates['CERT'])
+    [u'CRS1_100', u'CRS2_100', u'CRS2_200']
+
+    >>> list(a.courses)
+    [u'CRS1']
+
+    >>> del root['app']['faculties']['FAC']['DEPT'].courses['CRS1']
+    >>> list(a.certificates['CERT'])
+    [u'CRS2_100', u'CRS2_200']
+
+    >>> list(a.courses)
+    []
+
+If we remove a referrer of a course, this will not touch the course
+nor other referrers:
+
+    >>> del fac['DEPT'].certificates['CERT']['CRS2_200']
+    >>> list(a.certificates['CERT'])
+    [u'CRS2_100']
+
+    >>> list(fac['DEPT2'].courses)
+    [u'CRS2']
Index: main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/department.py
===================================================================
--- main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/department.py	(revision 9209)
+++ main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/department.py	(revision 9211)
@@ -32,16 +32,15 @@
     grok.implements(IDepartment, IDepartmentAdd)
 
-    local_roles = [
-        'waeup.local.DepartmentManager',
-        'waeup.local.ClearanceOfficer',
-        'waeup.local.UGClearanceOfficer',
-        'waeup.local.PGClearanceOfficer',
-        'waeup.local.CourseAdviser100',
-        'waeup.local.CourseAdviser200',
-        'waeup.local.CourseAdviser300',
-        'waeup.local.CourseAdviser400',
-        'waeup.local.CourseAdviser500',
-        'waeup.local.CourseAdviser600',
-        ]
+    @property       # Make this method read_only and looking like an attr.
+    def local_roles(self):
+        return ['waeup.local.DepartmentManager',
+                'waeup.local.ClearanceOfficer',
+                'waeup.local.CourseAdviser100',
+                'waeup.local.CourseAdviser200',
+                'waeup.local.CourseAdviser300',
+                'waeup.local.CourseAdviser400',
+                'waeup.local.CourseAdviser500',
+                'waeup.local.CourseAdviser600',
+                ]
 
     # A simple counter for ids.
Index: main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/export.py
===================================================================
--- main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/export.py	(revision 9209)
+++ main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/export.py	(revision 9211)
@@ -19,5 +19,4 @@
 """
 import grok
-from zope.securitypolicy.interfaces import IPrincipalRoleMap
 from waeup.kofa.interfaces import ICSVExporter
 from waeup.kofa.interfaces import MessageFactory as _
@@ -31,19 +30,8 @@
 
     #: Fieldnames considered by this exporter
-    fields = ('code', 'title', 'title_prefix', 'users_with_local_roles')
+    fields = ('code', 'title', 'title_prefix')
 
     #: The title under which this exporter will be displayed
     title = _(u'Faculties')
-
-    def mangle_value(self, value, name, context=None):
-        """Hook for mangling values in derived classes
-        """
-        if name == 'users_with_local_roles':
-            value = []
-            role_map = IPrincipalRoleMap(context)
-            for local_role, user_name, setting in role_map.getPrincipalsAndRoles():
-                value.append({'user_name':user_name,'local_role':local_role})
-        return super(FacultyExporter, self).mangle_value(
-            value, name, context)
 
     def export(self, faculties, filepath=None):
@@ -62,4 +50,5 @@
         If `filepath` is ``None``, a raw string with CSV data is returned.
         """
+        writer, outfile = self.get_csv_writer(filepath)
         faculties = site.get('faculties', {})
         return self.export(faculties.values(), filepath)
@@ -72,6 +61,5 @@
 
     #: Fieldnames considered by this exporter
-    fields = ('code', 'faculty_code', 'title', 'title_prefix',
-        'users_with_local_roles')
+    fields = ('code', 'faculty_code', 'title', 'title_prefix')
 
     #: The title under which this exporter will be displayed
@@ -89,5 +77,5 @@
 
     def export_all(self, site, filepath=None):
-        """Export departments in faculty into filepath as CSV data.
+        """Export faculties in facultycontainer into filepath as CSV data.
 
         If `filepath` is ``None``, a raw string with CSV data is returned.
@@ -109,5 +97,5 @@
     #: Fieldnames considered by this exporter
     fields = ('code', 'faculty_code', 'department_code', 'title', 'credits',
-              'passmark', 'semester', 'users_with_local_roles')
+              'passmark', 'semester')
 
     #: The title under which this exporter will be displayed
@@ -117,10 +105,5 @@
         """Hook for mangling values in derived classes
         """
-        if name == 'users_with_local_roles':
-            value = []
-            role_map = IPrincipalRoleMap(context)
-            for local_role, user_name, setting in role_map.getPrincipalsAndRoles():
-                value.append({'user_name':user_name,'local_role':local_role})
-        elif name == 'faculty_code':
+        if name == 'faculty_code':
             try:
                 value = context.__parent__.__parent__.__parent__.code
@@ -136,5 +119,5 @@
 
     def export_all(self, site, filepath=None):
-        """Export courses into filepath as CSV data.
+        """Export faculties in facultycontainer into filepath as CSV data.
 
         If `filepath` is ``None``, a raw string with CSV data is returned.
@@ -157,6 +140,5 @@
     fields = ('code', 'faculty_code', 'department_code', 'title', 'study_mode',
               'start_level', 'end_level', 'application_category',
-              'school_fee_1', 'school_fee_2', 'school_fee_3', 'school_fee_4',
-              'users_with_local_roles')
+              'school_fee_1', 'school_fee_2')
 
     #: The title under which this exporter will be displayed
@@ -164,5 +146,5 @@
 
     def export_all(self, site, filepath=None):
-        """Export certificates into filepath as CSV data.
+        """Export faculties in facultycontainer into filepath as CSV data.
 
         If `filepath` is ``None``, a raw string with CSV data is returned.
@@ -211,5 +193,5 @@
 
     def export_all(self, site, filepath=None):
-        """Export certificate courses into filepath as CSV data.
+        """Export faculties in facultycontainer into filepath as CSV data.
 
         If `filepath` is ``None``, a raw string with CSV data is returned.
Index: main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/faculty.py
===================================================================
--- main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/faculty.py	(revision 9209)
+++ main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/faculty.py	(revision 9211)
@@ -32,16 +32,15 @@
     grok.implements(IFaculty, IFacultyAdd)
 
-    local_roles = [
-        'waeup.local.DepartmentManager',
-        'waeup.local.ClearanceOfficer',
-        'waeup.local.UGClearanceOfficer',
-        'waeup.local.PGClearanceOfficer',
-        'waeup.local.CourseAdviser100',
-        'waeup.local.CourseAdviser200',
-        'waeup.local.CourseAdviser300',
-        'waeup.local.CourseAdviser400',
-        'waeup.local.CourseAdviser500',
-        'waeup.local.CourseAdviser600',
-        ]
+    @property       # Make this method read_only and looking like an attr.
+    def local_roles(self):
+        return ['waeup.local.DepartmentManager',
+                'waeup.local.ClearanceOfficer',
+                'waeup.local.CourseAdviser100',
+                'waeup.local.CourseAdviser200',
+                'waeup.local.CourseAdviser300',
+                'waeup.local.CourseAdviser400',
+                'waeup.local.CourseAdviser500',
+                'waeup.local.CourseAdviser600',
+                ]
 
     def __init__(self,
Index: main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/interfaces.py
===================================================================
--- main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/interfaces.py	(revision 9209)
+++ main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/interfaces.py	(revision 9211)
@@ -240,14 +240,4 @@
         )
 
-    school_fee_3 = schema.Float(
-        title = _(u'Foreigner Initial School Fee'),
-        required = False,
-        )
-
-    school_fee_4 = schema.Float(
-        title = _(u'Foreigner Returning School Fee'),
-        required = False,
-        )
-
     def longtitle():
         """
@@ -296,5 +286,5 @@
     """
     course = schema.Choice(
-        title = _(u'Course'),
+        title = _(u'Course Referrer'),
         source = CourseSource(),
         readonly = True,
@@ -310,5 +300,5 @@
     mandatory = schema.Bool(
         title = _(u'Is mandatory course (not elective)'),
-        required = False,
+        required = True,
         default = True,
         )
Index: main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/tests/sample_certificate_data.csv
===================================================================
--- main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/tests/sample_certificate_data.csv	(revision 9209)
+++ 	(revision )
@@ -1,8 +1,0 @@
-faculty_code,department_code,code,local_roles
-FAC1,DEP1,CRT2,"[{'user_name':'anne','local_role':'waeup.local.CourseAdviser100'}]"
-FAC1,DEP1,CRT2,"[{'user_name':'alice','local_role':'waeup.local.CourseAdviser100'}]"
-FAC1,DEP1,CRT2,"[{'user_name':'anne','local_role':'waeup.local.Boss'}]"
-FAC1,DEP1,CRT2,"[('anything')]"
-FAC1,DEP1,CRT2,"["
-FAC1,DEP1,CRT2,"[{'user':'anne','local':'waeup.local.CourseAdviser100'}]"
-FAC11,DEP2,CRT2,"[{'user_name':'anne','local_role':'waeup.local.CourseAdviser100'}]"
Index: main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/tests/sample_course_data.csv
===================================================================
--- main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/tests/sample_course_data.csv	(revision 9209)
+++ 	(revision )
@@ -1,8 +1,0 @@
-faculty_code,department_code,code,local_roles
-FAC1,DEP1,CRS2,"[{'user_name':'anne','local_role':'waeup.local.Lecturer'}]"
-FAC1,DEP1,CRS2,"[{'user_name':'alice','local_role':'waeup.local.Lecturer'}]"
-FAC1,DEP1,CRS2,"[{'user_name':'anne','local_role':'waeup.local.Boss'}]"
-FAC1,DEP1,CRS2,"[('anything')]"
-FAC1,DEP1,CRS2,"["
-FAC1,DEP1,CRS2,"[{'user':'anne','local':'waeup.local.Lecturer'}]"
-FAC11,DEP2,CRS2,"[{'user_name':'anne','local_role':'waeup.local.Lecturer'}]"
Index: main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/tests/sample_department_data.csv
===================================================================
--- main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/tests/sample_department_data.csv	(revision 9209)
+++ 	(revision )
@@ -1,8 +1,0 @@
-faculty_code,code,local_roles
-FAC1,DEP2,"[{'user_name':'anne','local_role':'waeup.local.DepartmentManager'}]"
-FAC1,DEP2,"[{'user_name':'alice','local_role':'waeup.local.DepartmentManager'}]"
-FAC1,DEP2,"[{'user_name':'anne','local_role':'waeup.local.Boss'}]"
-FAC1,DEP2,"[('anything')]"
-FAC1,DEP2,"["
-FAC1,DEP2,"[{'user':'anne','local':'waeup.local.DepartmentManager'}]"
-FAC11,DEP2,"[{'user_name':'anne','local_role':'waeup.local.DepartmentManager'}]"
Index: main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/tests/sample_faculty_data.csv
===================================================================
--- main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/tests/sample_faculty_data.csv	(revision 9209)
+++ 	(revision )
@@ -1,7 +1,0 @@
-code,local_roles
-ABC,"[{'user_name':'bob','local_role':'waeup.local.DepartmentManager'}]"
-CDE,"[{'user_name':'alice','local_role':'waeup.local.DepartmentManager'}]"
-DEF,"[{'user_name':'bob','local_role':'waeup.local.Boss'}]"
-EFG,"[('anything')]"
-FGH,"["
-GHI,"[{'user':'bob','local':'waeup.local.DepartmentManager'}]"
Index: main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/tests/sample_faculty_data_update.csv
===================================================================
--- main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/tests/sample_faculty_data_update.csv	(revision 9209)
+++ 	(revision )
@@ -1,5 +1,0 @@
-code,local_roles
-FAC1,XXX
-FAC2,"[{'user_name':'bob','local_role':'waeup.local.DepartmentManager'}]"
-FAC3,<IGNORE>
-FAC4,
Index: main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/tests/test_batching.py
===================================================================
--- main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/tests/test_batching.py	(revision 9209)
+++ main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/tests/test_batching.py	(revision 9211)
@@ -19,16 +19,8 @@
 # Tests for university related batching
 import unittest
-import tempfile
-import shutil
-import os
-from zope.component.hooks import setSite, clearSite
-from zope.component import createObject
-from zope.securitypolicy.interfaces import (
-    IPrincipalRoleMap, IPrincipalRoleManager)
-from zope.testbrowser.testing import Browser
+
 from zope.interface.verify import verifyClass, verifyObject
 from waeup.kofa.interfaces import IBatchProcessor
 from waeup.kofa.testing import FunctionalTestCase, FunctionalLayer
-from waeup.kofa.app import University
 from waeup.kofa.university.batching import (
     FacultyProcessor, DepartmentProcessor, CourseProcessor,
@@ -36,103 +28,14 @@
 from waeup.kofa.university.certificate import Certificate, CertificateCourse
 from waeup.kofa.university.course import Course
-from waeup.kofa.university import Faculty, Department
-from waeup.kofa.university.batching import FacultyProcessor
-
-FACULTY_SAMPLE_DATA = open(
-    os.path.join(os.path.dirname(__file__), 'sample_faculty_data.csv'),
-    'rb').read()
-
-FACULTY_HEADER_FIELDS = FACULTY_SAMPLE_DATA.split(
-    '\n')[0].split(',')
-
-FACULTY_SAMPLE_DATA_UPDATE = open(
-    os.path.join(os.path.dirname(__file__), 'sample_faculty_data_update.csv'),
-    'rb').read()
-
-FACULTY_HEADER_FIELDS_UPDATE = FACULTY_SAMPLE_DATA_UPDATE.split(
-    '\n')[0].split(',')
-
-DEPARTMENT_SAMPLE_DATA = open(
-    os.path.join(os.path.dirname(__file__), 'sample_department_data.csv'),
-    'rb').read()
-
-DEPARTMENT_HEADER_FIELDS = DEPARTMENT_SAMPLE_DATA.split(
-    '\n')[0].split(',')
-
-CERTIFICATE_SAMPLE_DATA = open(
-    os.path.join(os.path.dirname(__file__), 'sample_certificate_data.csv'),
-    'rb').read()
-
-CERTIFICATE_HEADER_FIELDS = CERTIFICATE_SAMPLE_DATA.split(
-    '\n')[0].split(',')
-
-COURSE_SAMPLE_DATA = open(
-    os.path.join(os.path.dirname(__file__), 'sample_course_data.csv'),
-    'rb').read()
-
-COURSE_HEADER_FIELDS = COURSE_SAMPLE_DATA.split(
-    '\n')[0].split(',')
-
-class UniversityProcessorSetup(FunctionalTestCase):
-
-    layer = FunctionalLayer
-
-    def setUp(self):
-        super(UniversityProcessorSetup, self).setUp()
-        self.dc_root = tempfile.mkdtemp()
-        self.workdir = tempfile.mkdtemp()
-        app = University()
-        self.getRootFolder()['app'] = app
-        self.app = self.getRootFolder()['app']
-        setSite(app)
-
-        self.app['users'].addUser('bob', 'bobssecret')
-        self.app['users'].addUser('anne', 'annessecret')
-
-        # Populate university
-        self.certificate = createObject('waeup.Certificate')
-        self.certificate.code = 'CRT1'
-        self.app['faculties']['FAC1'] = Faculty(code='FAC1')
-        self.app['faculties']['FAC1']['DEP1'] = Department(code='DEP1')
-        self.app['faculties']['FAC1']['DEP1'].certificates.addCertificate(
-            self.certificate)
-        self.course = createObject('waeup.Course')
-        self.course.code = 'CRS1'
-        self.app['faculties']['FAC1']['DEP1'].courses.addCourse(
-            self.course)
-        #self.app['faculties']['fac1']['dep1'].certificates['CERT1'].addCertCourse(
-        #    self.course, level=100)
-        
-        self.logfile = os.path.join(
-            self.app['datacenter'].storage, 'logs', 'main.log')
-        return
-        return
-
-    def tearDown(self):
-        super(UniversityProcessorSetup, self).tearDown()
-        shutil.rmtree(self.workdir)
-        shutil.rmtree(self.dc_root)
-        clearSite()
-        return
-
-class TestFacultyProcessor(UniversityProcessorSetup):
-
-    def setUp(self):
-        super(TestFacultyProcessor, self).setUp()
-
-        self.browser = Browser()
-        self.browser.handleErrors = False
-        self.datacenter_path = 'http://localhost/app/datacenter'
-        self.app['datacenter'].setStoragePath(self.dc_root)
-
+from waeup.kofa.university.department import Department
+
+
+class TestFacultyProcessor(unittest.TestCase):
+
+    def setUp(self):
         self.proc = FacultyProcessor()
         self.site1 = dict(faculties=dict())
         self.site2 = dict(faculties=dict(FAC='pseudo faculty'))
         self.row = dict(code='FAC')
-
-        self.csv_file_faculty = os.path.join(self.workdir, 'sample_faculty_data.csv')
-        open(self.csv_file_faculty, 'wb').write(FACULTY_SAMPLE_DATA)
-        self.csv_file_faculty_update = os.path.join(self.workdir, 'sample_faculty_data_update.csv')
-        open(self.csv_file_faculty_update, 'wb').write(FACULTY_SAMPLE_DATA_UPDATE)
         return
 
@@ -157,195 +60,7 @@
         return
 
-    def test_checkConversion(self):
-        # Make sure we can check conversions.
-        errs, inv_errs, conv_dict = self.proc.checkConversion(
-            dict(faculty_code='ABC', local_roles='[]'))
-        self.assertEqual(len(errs),0)
-
-        errs, inv_errs, conv_dict = self.proc.checkConversion(
-            dict(faculty_code='ABC',
-            local_roles="['nonsense'"
-            ))
-        self.assertEqual(len(errs),1)
-        self.assertEqual(errs, [('local_roles', 'Error')])
-        errs, inv_errs, conv_dict = self.proc.checkConversion(
-            dict(faculty_code='ABC',
-            local_roles="('abc')"
-            ))
-        self.assertEqual(len(errs),1)
-        self.assertEqual(errs, [('local_roles', 'no list')])
-        errs, inv_errs, conv_dict = self.proc.checkConversion(
-            dict(faculty_code='ABC',
-            local_roles="[('ABC')]"
-            ))
-        self.assertEqual(len(errs),1)
-        self.assertEqual(errs, [('local_roles', 'no dicts')])
-        errs, inv_errs, conv_dict = self.proc.checkConversion(
-            dict(faculty_code='ABC',
-            local_roles="('abc')"
-            ))
-        self.assertEqual(len(errs),1)
-        self.assertEqual(errs, [('local_roles', 'no list')])
-        errs, inv_errs, conv_dict = self.proc.checkConversion(
-            dict(faculty_code='ABC', local_roles=
-            "[{'name':'bob','local_role':'waeup.local.DepartmentManager'},]"
-            ))
-        self.assertEqual(len(errs),1)
-        self.assertEqual(errs, [('local_roles', 'user_name or local_role missing')])
-        errs, inv_errs, conv_dict = self.proc.checkConversion(
-            dict(faculty_code='ABC', local_roles=
-            "[{'user_name':'bob','localrole':'waeup.local.DepartmentManager'},]"
-            ))
-        self.assertEqual(len(errs),1)
-        self.assertEqual(errs, [('local_roles', 'user_name or local_role missing')])
-        errs, inv_errs, conv_dict = self.proc.checkConversion(
-            dict(faculty_code='ABC', local_roles=
-            "[{'user_name':'bob','local_role':'waeup.local.Boss'},]"
-            ))
-        self.assertEqual(len(errs),1)
-        self.assertEqual(errs, [('local_roles', 'waeup.local.Boss not allowed')])
-        errs, inv_errs, conv_dict = self.proc.checkConversion(
-            dict(faculty_code='ABC', local_roles=
-            "[{'user_name':'john','local_role':'waeup.local.DepartmentManager'},]"
-            ))
-        self.assertEqual(len(errs),1)
-        self.assertEqual(errs, [('local_roles', 'john does not exist')])
-        errs, inv_errs, conv_dict = self.proc.checkConversion(
-            dict(faculty_code='ABC', local_roles=
-            "[{'user_name':'bob','local_role':'waeup.local.DepartmentManager'},]"
-            ))
-        self.assertEqual(len(errs),0)
-        return
-
-    def test_import(self):
-        num, num_warns, fin_file, fail_file = self.proc.doImport(
-            self.csv_file_faculty, FACULTY_HEADER_FIELDS)
-        content = open(fail_file).read()
-        self.assertEqual(num_warns,5)
-        self.assertEqual(
-            content,
-            'code,local_roles,--ERRORS--\r\n'
-            'CDE,"[{\'user_name\':\'alice\',\'local_role\':\'waeup.local.DepartmentManager\'}]",'
-            'local_roles: alice does not exist\r\n'
-            'DEF,"[{\'user_name\':\'bob\',\'local_role\':\'waeup.local.Boss\'}]",'
-            'local_roles: waeup.local.Boss not allowed\r\n'
-            'EFG,[(\'anything\')],local_roles: no dicts\r\n'
-            'FGH,[,local_roles: Error\r\n'
-            'GHI,"[{\'user\':\'bob\',\'local\':\'waeup.local.DepartmentManager\'}]",'
-            'local_roles: user_name or local_role missing\r\n'
-            )
-        # Bob got a local role in faculty ABC.
-        abc = self.app['faculties']['ABC']
-        role_map = IPrincipalRoleMap(abc)
-        local_role, user_name, setting = role_map.getPrincipalsAndRoles()[0]
-        self.assertEqual(user_name, 'bob')
-        self.assertEqual(local_role, 'waeup.local.DepartmentManager')
-        shutil.rmtree(os.path.dirname(fin_file))
-        return
-
-    def test_import_update(self):
-        self.app['faculties']['FAC2'] = Faculty(code='FAC2')
-        self.app['faculties']['FAC3'] = Faculty(code='FAC3')
-        self.app['faculties']['FAC4'] = Faculty(code='FAC4')
-
-        role_manager1 = IPrincipalRoleManager(self.app['faculties']['FAC1'])
-        role_manager1.assignRoleToPrincipal('alfonsrole', 'alfons')
-        role_map1 = IPrincipalRoleMap(self.app['faculties']['FAC1'])
-        self.assertEqual(len(role_map1.getPrincipalsAndRoles()), 1)
-
-        role_manager2 = IPrincipalRoleManager(self.app['faculties']['FAC2'])
-        role_manager2.assignRoleToPrincipal('alfonsrole', 'alfons')
-        role_map2 = IPrincipalRoleMap(self.app['faculties']['FAC2'])
-        self.assertEqual(len(role_map2.getPrincipalsAndRoles()), 1)
-
-        role_manager3 = IPrincipalRoleManager(self.app['faculties']['FAC3'])
-        role_manager3.assignRoleToPrincipal('alfonsrole', 'alfons')
-        role_map3 = IPrincipalRoleMap(self.app['faculties']['FAC3'])
-        self.assertEqual(len(role_map3.getPrincipalsAndRoles()), 1)
-
-        role_manager4 = IPrincipalRoleManager(self.app['faculties']['FAC4'])
-        role_manager4.assignRoleToPrincipal('alfonsrole', 'alfons')
-        role_map4 = IPrincipalRoleMap(self.app['faculties']['FAC4'])
-        self.assertEqual(len(role_map4.getPrincipalsAndRoles()), 1)
-
-        local_role, user_name, setting = role_map2.getPrincipalsAndRoles()[0]
-        self.assertEqual(user_name, 'alfons')
-        self.assertEqual(local_role, 'alfonsrole')
-
-        num, num_warns, fin_file, fail_file = self.proc.doImport(
-            self.csv_file_faculty_update, FACULTY_HEADER_FIELDS_UPDATE, 'update')
-        self.assertEqual(num_warns,0)
-        # Local roles have been removed in FAC1 due to deletion marker.
-        self.assertEqual(len(role_map1.getPrincipalsAndRoles()), 0)
-        # Old local roles have been removed and new roles have been added in FAC2.
-        self.assertEqual(len(role_map2.getPrincipalsAndRoles()), 1)
-        local_role, user_name, setting = role_map2.getPrincipalsAndRoles()[0]
-        self.assertEqual(user_name, 'bob')
-        self.assertEqual(local_role, 'waeup.local.DepartmentManager')
-        # Local roles are not touched in FAC3 due to ignore marker.
-        self.assertEqual(len(role_map3.getPrincipalsAndRoles()), 1)
-        local_role, user_name, setting = role_map3.getPrincipalsAndRoles()[0]
-        self.assertEqual(user_name, 'alfons')
-        self.assertEqual(local_role, 'alfonsrole')
-        # Local roles are not touched in FAC4 due to empty cell.
-        self.assertEqual(len(role_map4.getPrincipalsAndRoles()), 1)
-        local_role, user_name, setting = role_map4.getPrincipalsAndRoles()[0]
-        self.assertEqual(user_name, 'alfons')
-        self.assertEqual(local_role, 'alfonsrole')
-        shutil.rmtree(os.path.dirname(fin_file))
-        return
-        
-    def test_import_update_logging(self):
-        self.app['faculties']['FAC2'] = Faculty(code='FAC2')
-        self.app['faculties']['FAC3'] = Faculty(code='FAC3')
-        self.app['faculties']['FAC4'] = Faculty(code='FAC4')
-        num, num_warns, fin_file, fail_file = self.proc.doImport(
-            self.csv_file_faculty_update, FACULTY_HEADER_FIELDS_UPDATE, 'update')
-        self.assertEqual(num_warns,0)
-        logcontent = open(self.logfile).read()
-        # Logging message from updateEntry
-        self.assertTrue(
-            'INFO - system - Department Processor - DEP2 - '
-            'Record updated: local_roles=anne|waeup.local.DepartmentManager, '
-            'code=DEP2'
-            in logcontent)
-
-    def test_upload_import_reupload(self):
-        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
-        self.browser.open(self.datacenter_path)
-        self.assertEqual(self.browser.headers['Status'], '200 Ok')
-        self.assertEqual(self.browser.url, self.datacenter_path)
-        self.browser.getLink("Upload data").click()
-        file = open(self.csv_file_faculty)
-        ctrl = self.browser.getControl(name='uploadfile:file')
-        file_ctrl = ctrl.mech_control
-        file_ctrl.add_file(file, filename='sample_faculty_data.csv')
-        self.browser.getControl('Upload').click()
-        self.browser.getLink('Process data').click()
-        self.browser.getControl(name="select").click()
-        importerselect = self.browser.getControl(name='importer')
-        importerselect.getControl('Faculty Processor').selected = True
-        modeselect = self.browser.getControl(name='mode')
-        modeselect.getControl(value='create').selected = True
-        self.browser.getControl('Proceed to step 3').click()
-        self.assertTrue('Header fields OK' in self.browser.contents)
-        self.browser.getControl('Perform import').click()
-        self.assertTrue('Successfully processed 1 rows' in self.browser.contents)
-        # We import the same file a second time.
-        self.browser.open(self.datacenter_path)
-        self.browser.getLink("Upload data").click()
-        file = open(self.csv_file_faculty)
-        ctrl = self.browser.getControl(name='uploadfile:file')
-        file_ctrl = ctrl.mech_control
-        file_ctrl.add_file(file, filename='sample_faculty_data.csv')
-        self.browser.getControl('Upload').click()
-        self.assertTrue(
-            'File with same name was uploaded earlier' in self.browser.contents)
-        return
-
-class TestDepartmentProcessor(UniversityProcessorSetup):
-
-    def setUp(self):
-        super(TestDepartmentProcessor, self).setUp()
+class TestDepartmentProcessors(unittest.TestCase):
+
+    def setUp(self):
         self.proc = DepartmentProcessor()
         self.site0 = dict()
@@ -354,7 +69,4 @@
         self.site3 = dict(faculties=dict(FAC=dict(DPT='pseudo department')))
         self.row = dict(code='DPT', faculty_code='FAC')
-
-        self.csv_file_department = os.path.join(self.workdir, 'sample_department_data.csv')
-        open(self.csv_file_department, 'wb').write(DEPARTMENT_SAMPLE_DATA)
         return
 
@@ -403,44 +115,21 @@
         return
 
-    def test_import(self):
-        num, num_warns, fin_file, fail_file = self.proc.doImport(
-            self.csv_file_department, DEPARTMENT_HEADER_FIELDS)
-        content = open(fail_file).read()
-        self.assertEqual(num_warns,6)
-        self.assertEqual(
-            content,
-            'faculty_code,code,local_roles,--ERRORS--\r\n'
-            'FAC1,DEP2,"[{\'user_name\':\'alice\',\'local_role\':\'waeup.local.DepartmentManager\'}]",'
-            'local_roles: alice does not exist\r\n'
-            'FAC1,DEP2,"[{\'user_name\':\'anne\',\'local_role\':\'waeup.local.Boss\'}]",'
-            'local_roles: waeup.local.Boss not allowed\r\n'
-            'FAC1,DEP2,[(\'anything\')],local_roles: no dicts\r\n'
-            'FAC1,DEP2,[,local_roles: Error\r\n'
-            'FAC1,DEP2,"[{\'user\':\'anne\',\'local\':\'waeup.local.DepartmentManager\'}]",'
-            'local_roles: user_name or local_role missing\r\n'
-            'FAC11,DEP2,"[{\'user_name\':\'anne\',\'local_role\':\'waeup.local.DepartmentManager\'}]",'
-            'Not all parents do exist yet. Skipping\r\n'
-            )
-        # Anne got a local role in department DEP2.
-        dep = self.app['faculties']['FAC1']['DEP2']
-        role_map = IPrincipalRoleMap(dep)
-        local_role, user_name, setting = role_map.getPrincipalsAndRoles()[0]
-        self.assertEqual(user_name, 'anne')
-        self.assertEqual(local_role, 'waeup.local.DepartmentManager')
-        shutil.rmtree(os.path.dirname(fin_file))
-        return
-
-class CourseProcessorTests(UniversityProcessorSetup):
+class CourseProcessorTests(FunctionalTestCase):
+
+    layer = FunctionalLayer
 
     def setUp(self):
         super(CourseProcessorTests, self).setUp()
         self.proc = CourseProcessor()
-        self.row1 = dict(department_code='DEP2', faculty_code='FAC1', code="CRS1")
-        self.row2 = dict(department_code='DEP1', faculty_code='FAC2', code="CRS1")
-        self.row3 = dict(department_code='DEP1', faculty_code='FAC1', code="CRS2")
-        self.row4 = dict(department_code='DEP1', faculty_code='FAC1', code="CRS1")
-
-        self.csv_file_course = os.path.join(self.workdir, 'sample_course_data.csv')
-        open(self.csv_file_course, 'wb').write(COURSE_SAMPLE_DATA)
+        self.site0 = dict()
+        self.site1 = dict(faculties=dict())
+        self.site2 = dict(faculties=dict(FAC=dict()))
+        self.department1 = Department(code='DPT')
+        self.department2 = Department(code='DPT')
+        self.course = Course(code='CRS')
+        self.department2.courses['CRS'] = self.course
+        self.site3 = dict(faculties=dict(FAC=dict(DPT=self.department1)))
+        self.site4 = dict(faculties=dict(FAC=dict(DPT=self.department2)))
+        self.row = dict(department_code='DPT', faculty_code='FAC', code="CRS")
         return
 
@@ -453,18 +142,24 @@
     def test_parents_exist(self):
         # make sure we lookup parents correctly
-        result1 = self.proc.parentsExist(self.row1, self.app)
-        result2 = self.proc.parentsExist(self.row2, self.app)
-        result3 = self.proc.parentsExist(self.row3, self.app)
+        result0 = self.proc.parentsExist(self.row, self.site0)
+        result1 = self.proc.parentsExist(self.row, self.site1)
+        result2 = self.proc.parentsExist(self.row, self.site2)
+        result3 = self.proc.parentsExist(self.row, self.site3)
+        result4 = self.proc.parentsExist(self.row, self.site4)
+        self.assertTrue(result0 is False)
         self.assertTrue(result1 is False)
         self.assertTrue(result2 is False)
         self.assertTrue(result3 is True)
+        self.assertTrue(result4 is True)
         return
 
     def test_entry_exists(self):
         # make sure we find an entry if it exists
-        result1 = self.proc.entryExists(self.row1, self.app)
-        result2 = self.proc.entryExists(self.row2, self.app)
-        result3 = self.proc.entryExists(self.row3, self.app)
-        result4 = self.proc.entryExists(self.row4, self.app)
+        result0 = self.proc.entryExists(self.row, self.site0)
+        result1 = self.proc.entryExists(self.row, self.site1)
+        result2 = self.proc.entryExists(self.row, self.site2)
+        result3 = self.proc.entryExists(self.row, self.site3)
+        result4 = self.proc.entryExists(self.row, self.site4)
+        self.assertTrue(result0 is False)
         self.assertTrue(result1 is False)
         self.assertTrue(result2 is False)
@@ -475,6 +170,6 @@
     def test_get_entry(self):
         # make sure we can get an entry if it exists
-        result1 = self.proc.getEntry(self.row1, self.app)
-        result2 = self.proc.getEntry(self.row4, self.app)
+        result1 = self.proc.getEntry(self.row, self.site3)
+        result2 = self.proc.getEntry(self.row, self.site4)
         self.assertTrue(result1 is None)
         self.assertTrue(result2 is self.course)
@@ -483,74 +178,56 @@
     def test_del_entry(self):
         # make sure we can delete entries
-        self.assertTrue('CRS1' in self.app['faculties']['FAC1']['DEP1'].courses.keys())
-        self.proc.delEntry(self.row4, self.app)
-        self.assertTrue('CRS1' not in self.app['faculties']['FAC1']['DEP1'].courses.keys())
-        return
-
-    def test_import(self):
-        num, num_warns, fin_file, fail_file = self.proc.doImport(
-            self.csv_file_course, COURSE_HEADER_FIELDS)
-        content = open(fail_file).read()
-        self.assertEqual(num_warns,6)
-        self.assertEqual(
-            content,
-            'faculty_code,department_code,code,local_roles,--ERRORS--\r\n'
-            'FAC1,DEP1,CRS2,"[{\'user_name\':\'alice\',\'local_role\':\'waeup.local.Lecturer\'}]",'
-            'local_roles: alice does not exist\r\n'
-            'FAC1,DEP1,CRS2,"[{\'user_name\':\'anne\',\'local_role\':\'waeup.local.Boss\'}]",'
-            'local_roles: waeup.local.Boss not allowed\r\n'
-            'FAC1,DEP1,CRS2,[(\'anything\')],local_roles: no dicts\r\n'
-            'FAC1,DEP1,CRS2,[,local_roles: Error\r\n'
-            'FAC1,DEP1,CRS2,"[{\'user\':\'anne\',\'local\':\'waeup.local.Lecturer\'}]",'
-            'local_roles: user_name or local_role missing\r\n'
-            'FAC11,DEP2,CRS2,"[{\'user_name\':\'anne\',\'local_role\':\'waeup.local.Lecturer\'}]",'
-            'Not all parents do exist yet. Skipping\r\n'
-            )
-        # Anne got a local role in course CRS2.
-        dep = self.app['faculties']['FAC1']['DEP1'].courses['CRS2']
-        role_map = IPrincipalRoleMap(dep)
-        local_role, user_name, setting = role_map.getPrincipalsAndRoles()[0]
-        self.assertEqual(user_name, 'anne')
-        self.assertEqual(local_role, 'waeup.local.Lecturer')
-        shutil.rmtree(os.path.dirname(fin_file))
-        return
-
-
-class CertificateProcessorTests(UniversityProcessorSetup):
+        self.assertTrue('CRS' in self.department2.courses.keys())
+        self.proc.delEntry(self.row, self.site4)
+        self.assertTrue('CRS' not in self.department2.courses.keys())
+        return
+
+class CertificateProcessorTests(FunctionalTestCase):
+
+    layer = FunctionalLayer
 
     def setUp(self):
         super(CertificateProcessorTests, self).setUp()
         self.proc = CertificateProcessor()
-        self.row1 = dict(department_code='DEP2', faculty_code='FAC1', code="CRT1")
-        self.row2 = dict(department_code='DEP1', faculty_code='FAC2', code="CRT1")
-        self.row3 = dict(department_code='DEP1', faculty_code='FAC1', code="CRT2")
-        self.row4 = dict(department_code='DEP1', faculty_code='FAC1', code="CRT1")
-
-        self.csv_file_certificate = os.path.join(self.workdir, 'sample_certificate_data.csv')
-        open(self.csv_file_certificate, 'wb').write(CERTIFICATE_SAMPLE_DATA)
-        return
-
-    def test_ifaces(self):
-        # Make sure we fullfill all interface contracts
-        verifyClass(IBatchProcessor, CourseProcessor)
+        self.site0 = dict()
+        self.site1 = dict(faculties=dict())
+        self.site2 = dict(faculties=dict(FAC=dict()))
+        self.department1 = Department(code='DPT')
+        self.department2 = Department(code='DPT')
+        self.certificate = Certificate(code='CRT')
+        self.department2.certificates['CRT'] = self.certificate
+        self.site3 = dict(faculties=dict(FAC=dict(DPT=self.department1)))
+        self.site4 = dict(faculties=dict(FAC=dict(DPT=self.department2)))
+        self.row = dict(department_code='DPT', faculty_code='FAC', code="CRT")
+        return
+
+    def test_ifaces(self):
+        # Make sure we fullfill all interface contracts
+        verifyClass(IBatchProcessor, CertificateProcessor)
         verifyObject(IBatchProcessor, self.proc)
         return
 
     def test_parents_exist(self):
-        # make sure we lookup parents correctly
-        result1 = self.proc.parentsExist(self.row1, self.app)
-        result2 = self.proc.parentsExist(self.row2, self.app)
-        result3 = self.proc.parentsExist(self.row3, self.app)
+        # make sure we can find all certificate parents
+        result0 = self.proc.parentsExist(self.row, self.site0)
+        result1 = self.proc.parentsExist(self.row, self.site1)
+        result2 = self.proc.parentsExist(self.row, self.site2)
+        result3 = self.proc.parentsExist(self.row, self.site3)
+        result4 = self.proc.parentsExist(self.row, self.site4)
+        self.assertTrue(result0 is False)
         self.assertTrue(result1 is False)
         self.assertTrue(result2 is False)
         self.assertTrue(result3 is True)
+        self.assertTrue(result4 is True)
         return
 
     def test_entry_exists(self):
         # make sure we find an entry if it exists
-        result1 = self.proc.entryExists(self.row1, self.app)
-        result2 = self.proc.entryExists(self.row2, self.app)
-        result3 = self.proc.entryExists(self.row3, self.app)
-        result4 = self.proc.entryExists(self.row4, self.app)
+        result0 = self.proc.entryExists(self.row, self.site0)
+        result1 = self.proc.entryExists(self.row, self.site1)
+        result2 = self.proc.entryExists(self.row, self.site2)
+        result3 = self.proc.entryExists(self.row, self.site3)
+        result4 = self.proc.entryExists(self.row, self.site4)
+        self.assertTrue(result0 is False)
         self.assertTrue(result1 is False)
         self.assertTrue(result2 is False)
@@ -561,6 +238,6 @@
     def test_get_entry(self):
         # make sure we can get an entry if it exists
-        result1 = self.proc.getEntry(self.row1, self.app)
-        result2 = self.proc.getEntry(self.row4, self.app)
+        result1 = self.proc.getEntry(self.row, self.site3)
+        result2 = self.proc.getEntry(self.row, self.site4)
         self.assertTrue(result1 is None)
         self.assertTrue(result2 is self.certificate)
@@ -569,45 +246,8 @@
     def test_del_entry(self):
         # make sure we can delete entries
-        self.assertTrue('CRT1' in self.app['faculties']['FAC1']['DEP1'].certificates.keys())
-        self.proc.delEntry(self.row4, self.app)
-        self.assertTrue('CRT1' not in self.app['faculties']['FAC1']['DEP1'].certificates.keys())
-        return
-
-    def test_import(self):
-        num, num_warns, fin_file, fail_file = self.proc.doImport(
-            self.csv_file_certificate, CERTIFICATE_HEADER_FIELDS)
-        content = open(fail_file).read()
-        self.assertEqual(num_warns,6)
-        self.assertEqual(
-            content,
-            'faculty_code,department_code,code,local_roles,--ERRORS--\r\n'
-            'FAC1,DEP1,CRT2,"[{\'user_name\':\'alice\',\'local_role\':\'waeup.local.CourseAdviser100\'}]",'
-            'local_roles: alice does not exist\r\n'
-            'FAC1,DEP1,CRT2,"[{\'user_name\':\'anne\',\'local_role\':\'waeup.local.Boss\'}]",'
-            'local_roles: waeup.local.Boss not allowed\r\n'
-            'FAC1,DEP1,CRT2,[(\'anything\')],local_roles: no dicts\r\n'
-            'FAC1,DEP1,CRT2,[,local_roles: Error\r\n'
-            'FAC1,DEP1,CRT2,"[{\'user\':\'anne\',\'local\':\'waeup.local.CourseAdviser100\'}]",'
-            'local_roles: user_name or local_role missing\r\n'
-            'FAC11,DEP2,CRT2,"[{\'user_name\':\'anne\',\'local_role\':\'waeup.local.CourseAdviser100\'}]",'
-            'Not all parents do exist yet. Skipping\r\n'
-            )
-        # Anne got a local role in certificate CRT2.
-        dep = self.app['faculties']['FAC1']['DEP1'].certificates['CRT2']
-        role_map = IPrincipalRoleMap(dep)
-        local_role, user_name, setting = role_map.getPrincipalsAndRoles()[0]
-        self.assertEqual(user_name, 'anne')
-        self.assertEqual(local_role, 'waeup.local.CourseAdviser100')
-        shutil.rmtree(os.path.dirname(fin_file))
-        
-        logcontent = open(self.logfile).read()
-        # Logging message from updateEntry
-        self.assertTrue(
-            'INFO - system - Certificate Processor - CRT2 - Record updated: '
-            'local_roles=anne|waeup.local.CourseAdviser100, code=CRT2'
-            in logcontent)        
-        
-        return
-
+        self.assertTrue('CRT' in self.department2.certificates.keys())
+        self.proc.delEntry(self.row, self.site4)
+        self.assertTrue('CRT' not in self.department2.certificates.keys())
+        return
 
 class CertCourseProcessorTests(FunctionalTestCase):
@@ -626,5 +266,5 @@
         self.department2.certificates['CRT'] = self.certificate
         self.course = Course(code='CRS')
-        self.department2.certificates['CRT'].addCertCourse(self.course)
+        self.department2.certificates['CRT'].addCourseRef(self.course)
         self.cert_course = self.certificate['CRS_100']
         self.department2.courses['CRS'] = self.course
Index: main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/tests/test_export.py
===================================================================
--- main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/tests/test_export.py	(revision 9209)
+++ main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/tests/test_export.py	(revision 9211)
@@ -22,7 +22,6 @@
 from zope.component import queryUtility
 from zope.interface.verify import verifyObject, verifyClass
-from zope.securitypolicy.interfaces import IPrincipalRoleManager
 from waeup.kofa.interfaces import ICSVExporter
-from waeup.kofa.testing import KofaUnitTestLayer, FunctionalLayer
+from waeup.kofa.testing import KofaUnitTestLayer
 from waeup.kofa.university import (
     FacultiesContainer, Faculty, Department, Course, Certificate,
@@ -35,5 +34,5 @@
 class FacultyExporterTest(unittest.TestCase):
 
-    layer = FunctionalLayer
+    layer = KofaUnitTestLayer
 
     def setUp(self):
@@ -67,6 +66,6 @@
         self.assertEqual(
             result,
-            'code,title,title_prefix,users_with_local_roles\r\n'
-            'F1,Faculty of Cheese,faculty,[]\r\n'
+            'code,title,title_prefix\r\n'
+            'F1,Faculty of Cheese,faculty\r\n'
             )
         return
@@ -79,6 +78,6 @@
         self.assertEqual(
             result,
-            'code,title,title_prefix,users_with_local_roles\r\n'
-            'F1,Faculty of Cheese,faculty,[]\r\n'
+            'code,title,title_prefix\r\n'
+            'F1,Faculty of Cheese,faculty\r\n'
             )
         return
@@ -97,7 +96,7 @@
         self.assertEqual(
             result,
-            'code,title,title_prefix,users_with_local_roles\r\n'
-            'F1,Faculty of Cheese,faculty,[]\r\n'
-            'F2,Centre of Onion,centre,[]\r\n'
+            'code,title,title_prefix\r\n'
+            'F1,Faculty of Cheese,faculty\r\n'
+            'F2,Centre of Onion,centre\r\n'
             )
         return
@@ -115,7 +114,7 @@
         self.assertEqual(
             result,
-            'code,title,title_prefix,users_with_local_roles\r\n'
-            'F1,Faculty of Cheese,faculty,[]\r\n'
-            'F2,Centre of Onion,centre,[]\r\n'
+            'code,title,title_prefix\r\n'
+            'F1,Faculty of Cheese,faculty\r\n'
+            'F2,Centre of Onion,centre\r\n'
             )
         return
@@ -124,5 +123,5 @@
     # Tests for DepartmentExporter
 
-    layer = FunctionalLayer
+    layer = KofaUnitTestLayer
 
     def setUp(self):
@@ -142,6 +141,4 @@
         self.fac1.addDepartment(self.dept2)
         self.fac2.addDepartment(self.dept3)
-        role_manager = IPrincipalRoleManager(self.dept1)
-        role_manager.assignRoleToPrincipal(u'bobsrole', u'bob')
         return
 
@@ -170,7 +167,6 @@
         self.assertEqual(
             result,
-            'code,faculty_code,title,title_prefix,users_with_local_roles\r\n'
-            'D1,F1,Department of Cheddar,department,'
-            '"[{\'user_name\': u\'bob\', \'local_role\': u\'bobsrole\'}]"\r\n'
+            'code,faculty_code,title,title_prefix\r\n'
+            'D1,F1,Department of Cheddar,department\r\n'
             )
         return
@@ -182,8 +178,7 @@
         self.assertEqual(
             result,
-            'code,faculty_code,title,title_prefix,users_with_local_roles\r\n'
-            'D1,F1,Department of Cheddar,department,'
-            '"[{\'user_name\': u\'bob\', \'local_role\': u\'bobsrole\'}]"\r\n'
-            'D2,F1,Institue of Gouda,institute,[]\r\n'
+            'code,faculty_code,title,title_prefix\r\n'
+            'D1,F1,Department of Cheddar,department\r\n'
+            'D2,F1,Institue of Gouda,institute\r\n'
             )
         return
@@ -196,9 +191,8 @@
         self.assertEqual(
             result,
-            'code,faculty_code,title,title_prefix,users_with_local_roles\r\n'
-            'D1,F1,Department of Cheddar,department,'
-            '"[{\'user_name\': u\'bob\', \'local_role\': u\'bobsrole\'}]"\r\n'
-            'D2,F1,Institue of Gouda,institute,[]\r\n'
-            'D3,F2,Department of Rings,department,[]\r\n'
+            'code,faculty_code,title,title_prefix\r\n'
+            'D1,F1,Department of Cheddar,department\r\n'
+            'D2,F1,Institue of Gouda,institute\r\n'
+            'D3,F2,Department of Rings,department\r\n'
             )
         return
@@ -210,9 +204,8 @@
         self.assertEqual(
             result,
-            'code,faculty_code,title,title_prefix,users_with_local_roles\r\n'
-            'D1,F1,Department of Cheddar,department,'
-            '"[{\'user_name\': u\'bob\', \'local_role\': u\'bobsrole\'}]"\r\n'
-            'D2,F1,Institue of Gouda,institute,[]\r\n'
-            'D3,F2,Department of Rings,department,[]\r\n'
+            'code,faculty_code,title,title_prefix\r\n'
+            'D1,F1,Department of Cheddar,department\r\n'
+            'D2,F1,Institue of Gouda,institute\r\n'
+            'D3,F2,Department of Rings,department\r\n'
             )
         return
@@ -221,5 +214,5 @@
     # Tests for CourseExporter
 
-    layer = FunctionalLayer
+    layer = KofaUnitTestLayer
 
     def setUp(self):
@@ -267,7 +260,6 @@
         self.assertEqual(
             result,
-            'code,faculty_code,department_code,title,credits,'
-            'passmark,semester,users_with_local_roles\r\n'
-            'C1,F1,D1,Cheese Basics,0,40,1,[]\r\n'
+            'code,faculty_code,department_code,title,credits,passmark,semester\r\n'
+            'C1,F1,D1,Cheese Basics,0,40,1\r\n'
             )
         return
@@ -279,8 +271,7 @@
         self.assertEqual(
             result,
-            'code,faculty_code,department_code,title,credits,passmark,'
-            'semester,users_with_local_roles\r\n'
-            'C1,F1,D1,Cheese Basics,0,40,1,[]\r\n'
-            'C2,F1,D1,Advanced Cheese Making,0,40,1,[]\r\n'
+            'code,faculty_code,department_code,title,credits,passmark,semester\r\n'
+            'C1,F1,D1,Cheese Basics,0,40,1\r\n'
+            'C2,F1,D1,Advanced Cheese Making,0,40,1\r\n'
             )
         return
@@ -293,9 +284,8 @@
         self.assertEqual(
             result,
-            'code,faculty_code,department_code,title,credits,passmark,'
-            'semester,users_with_local_roles\r\n'
-            'C1,F1,D1,Cheese Basics,0,40,1,[]\r\n'
-            'C2,F1,D1,Advanced Cheese Making,0,40,1,[]\r\n'
-            'C3,F1,D2,Selling Cheese,0,40,1,[]\r\n'
+            'code,faculty_code,department_code,title,credits,passmark,semester\r\n'
+            'C1,F1,D1,Cheese Basics,0,40,1\r\n'
+            'C2,F1,D1,Advanced Cheese Making,0,40,1\r\n'
+            'C3,F1,D2,Selling Cheese,0,40,1\r\n'
             )
         return
@@ -307,9 +297,8 @@
         self.assertEqual(
             result,
-            'code,faculty_code,department_code,title,credits,passmark,'
-            'semester,users_with_local_roles\r\n'
-            'C1,F1,D1,Cheese Basics,0,40,1,[]\r\n'
-            'C2,F1,D1,Advanced Cheese Making,0,40,1,[]\r\n'
-            'C3,F1,D2,Selling Cheese,0,40,1,[]\r\n'
+            'code,faculty_code,department_code,title,credits,passmark,semester\r\n'
+            'C1,F1,D1,Cheese Basics,0,40,1\r\n'
+            'C2,F1,D1,Advanced Cheese Making,0,40,1\r\n'
+            'C3,F1,D2,Selling Cheese,0,40,1\r\n'
             )
         return
@@ -318,5 +307,5 @@
     # Tests for CertificateExporter
 
-    layer = FunctionalLayer
+    layer = KofaUnitTestLayer
 
     def setUp(self):
@@ -350,6 +339,4 @@
         self.dept1.certificates.addCertificate(self.cert2)
         self.dept2.certificates.addCertificate(self.cert3)
-        role_manager = IPrincipalRoleManager(self.cert1)
-        role_manager.assignRoleToPrincipal(u'bobsrole', u'bob')
         return
 
@@ -378,9 +365,6 @@
         self.assertEqual(
             result,
-            'code,faculty_code,department_code,title,study_mode,start_level,'
-            'end_level,application_category,school_fee_1,'
-            'school_fee_2,school_fee_3,school_fee_4,users_with_local_roles\r\n'
-            'CERT1,F1,D1,Master of Cheese,ct_ft,100,300,basic,,,,,'
-            '"[{\'user_name\': u\'bob\', \'local_role\': u\'bobsrole\'}]"\r\n'
+            'code,faculty_code,department_code,title,study_mode,start_level,end_level,application_category,school_fee_1,school_fee_2\r\n'
+            'CERT1,F1,D1,Master of Cheese,ct_ft,100,300,basic,,\r\n'
             )
         return
@@ -392,10 +376,7 @@
         self.assertEqual(
             result,
-            'code,faculty_code,department_code,title,study_mode,start_level,'
-            'end_level,application_category,school_fee_1,'
-            'school_fee_2,school_fee_3,school_fee_4,users_with_local_roles\r\n'
-            'CERT1,F1,D1,Master of Cheese,ct_ft,100,300,basic,,,,,'
-            '"[{\'user_name\': u\'bob\', \'local_role\': u\'bobsrole\'}]"\r\n'
-            'CERT2,F1,D1,Master of Cheddar,ct_ft,400,700,cest,,,,,[]\r\n'
+            'code,faculty_code,department_code,title,study_mode,start_level,end_level,application_category,school_fee_1,school_fee_2\r\n'
+            'CERT1,F1,D1,Master of Cheese,ct_ft,100,300,basic,,\r\n'
+            'CERT2,F1,D1,Master of Cheddar,ct_ft,400,700,cest,,\r\n'
             )
         return
@@ -408,12 +389,8 @@
         self.assertEqual(
             result,
-            'code,faculty_code,department_code,title,study_mode,start_level,'
-            'end_level,application_category,'
-            'school_fee_1,school_fee_2,school_fee_3,school_fee_4,'
-            'users_with_local_roles\r\n'
-            'CERT1,F1,D1,Master of Cheese,ct_ft,100,300,basic,,,,,'
-            '"[{\'user_name\': u\'bob\', \'local_role\': u\'bobsrole\'}]"\r\n'
-            'CERT2,F1,D1,Master of Cheddar,ct_ft,400,700,cest,,,,,[]\r\n'
-            'CERT3,F1,D2,Cert. of Rubbish,dp_pt,100,200,no,,,,,[]\r\n'
+            'code,faculty_code,department_code,title,study_mode,start_level,end_level,application_category,school_fee_1,school_fee_2\r\n'
+            'CERT1,F1,D1,Master of Cheese,ct_ft,100,300,basic,,\r\n'
+            'CERT2,F1,D1,Master of Cheddar,ct_ft,400,700,cest,,\r\n'
+            'CERT3,F1,D2,Cert. of Rubbish,dp_pt,100,200,no,,\r\n'
             )
         return
@@ -425,12 +402,8 @@
         self.assertEqual(
             result,
-            'code,faculty_code,department_code,title,study_mode,start_level,'
-            'end_level,application_category,'
-            'school_fee_1,school_fee_2,school_fee_3,school_fee_4,'
-            'users_with_local_roles\r\n'
-            'CERT1,F1,D1,Master of Cheese,ct_ft,100,300,basic,,,,,'
-            '"[{\'user_name\': u\'bob\', \'local_role\': u\'bobsrole\'}]"\r\n'
-            'CERT2,F1,D1,Master of Cheddar,ct_ft,400,700,cest,,,,,[]\r\n'
-            'CERT3,F1,D2,Cert. of Rubbish,dp_pt,100,200,no,,,,,[]\r\n'
+            'code,faculty_code,department_code,title,study_mode,start_level,end_level,application_category,school_fee_1,school_fee_2\r\n'
+            'CERT1,F1,D1,Master of Cheese,ct_ft,100,300,basic,,\r\n'
+            'CERT2,F1,D1,Master of Cheddar,ct_ft,400,700,cest,,\r\n'
+            'CERT3,F1,D2,Cert. of Rubbish,dp_pt,100,200,no,,\r\n'
             )
         return
@@ -471,7 +444,7 @@
         self.dept1.certificates.addCertificate(self.cert2)
         self.dept2.certificates.addCertificate(self.cert3)
-        self.cert1.addCertCourse(self.course1, 100, True)
-        self.cert1.addCertCourse(self.course2, 400, False)
-        self.cert3.addCertCourse(self.course3, 100, False)
+        self.cert1.addCourseRef(self.course1, 100, True)
+        self.cert1.addCourseRef(self.course2, 400, False)
+        self.cert3.addCourseRef(self.course3, 100, False)
         self.certcourse1 = self.cert1['C1_100']
         self.certcourse2 = self.cert1['C2_400']
Index: main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/tests/test_university.py
===================================================================
--- main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/tests/test_university.py	(revision 9209)
+++ main/waeup.kofa/branches/uli-zc-async/src/waeup/kofa/university/tests/test_university.py	(revision 9211)
@@ -33,5 +33,5 @@
 def test_suite():
     # collect doctests for university subpackage
-    suite = get_doctest_suite(['university/certcourses.txt',])
+    suite = get_doctest_suite(['university/courserefs.txt',])
     # add local unittests (actually only one)
     for testcase in [UniversitySubpackageTests,]:
