Index: in/waeup.kofa/trunk/src/waeup/kofa/browser/batchprocessing.txt
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/browser/batchprocessing.txt	(revision 12936)
+++ 	(revision )
@@ -1,675 +1,0 @@
-Batch Processing via Browser
-****************************
-
-Preliminaries:
-
-We define a function that looks up a form with several submit buttons
-for the one with a given value (this functionality is missing in
-zope.testbrowser):
-
-    >>> def lookup_submit_value(name, value, browser):
-    ...   """Find a button with a certain value."""
-    ...   for num in range(0, 100):
-    ...     try:
-    ...       button = browser.getControl(name=name, index=num)
-    ...       if button.value.endswith(value):
-    ...         return button
-    ...     except IndexError:
-    ...       break
-    ...   return None
-
-Create a site:
-
-    >>> from waeup.kofa.app import University
-    >>> getRootFolder()['app'] = University()
-    >>> from zope.component.hooks import setSite
-    >>> setSite(getRootFolder()['app'])
-
-Create a datacenter storage path:
-
-    >>> import os
-    >>> import tempfile
-    >>> dc_path = tempfile.mkdtemp()
-
-Log in:
-
-    >>> from zope.testbrowser.testing import Browser
-    >>> browser = Browser()
-    >>> browser.addHeader('Authorization', 'Basic mgr:mgrpw')
-    >>> browser.handleErrors = False
-
-Set datacenter path and deselect moving old data:
-
-    >>> browser.open('http://localhost/app')
-    >>> browser.getLink('Data Center').click()
-    >>> browser.getLink('Edit settings').click()
-    >>> browser.getControl(name='newpath').value = dc_path
-    >>> browser.getControl(name='move').value = False
-    >>> browser.getControl(name='save').click()
-
-Set non-usable datacenter path:
-
-    >>> browser.getLink('Edit settings').click()
-    >>> browser.getControl(name='newpath').value = '/'
-    >>> browser.getControl(name='save').click()
-    >>> 'Given storage path cannot be used.' in browser.contents
-    True
-    >>> browser.getControl('Back to Data Center').click()
-
-
-Batch Processing Faculties
-==========================
-
-Go to datacenter page:
-
-    >>> browser.open('http://localhost/app/datacenter')
-
-Prepare a CSV file for faculties (extended ascii values are accepted):
-
-    >>> open('faculties.csv', 'wb').write(
-    ... """code,title,title_prefix
-    ... FAC1,Faculty 1,faculty
-    ... FAC2,Faculty 2,institute
-    ... FAC3,Fäcülty 3,school
-    ... """)
-
-Upload the file:
-
-    >>> import cStringIO
-    >>> browser.getLink('Upload data').click()
-    >>> filecontents = cStringIO.StringIO(
-    ...   open('faculties.csv', 'rb').read())
-    >>> filewidget = browser.getControl(name='uploadfile:file')
-    >>> filewidget.add_file(filecontents, 'text/plain', 'faculties.csv')
-    >>> browser.getControl(name='SUBMIT').click()
-
-Step 1: start batch processing:
-
-    >>> browser.getLink('Process data').click()
-    >>> button = lookup_submit_value(
-    ...   'select', 'faculties_zope.mgr.csv', browser)
-    >>> button.click()
-
-Step 2: select a processor and mode:
-
-    >>> importerselect = browser.getControl(name='importer')
-    >>> importerselect.displayOptions
-    ['AccessCodeBatch Processor',
-    'AccessCode Processor',
-    'Applicant Processor',
-    'ApplicantsContainer Processor',
-    'CertificateCourse Processor',
-    'Certificate Processor',
-    'Course Processor',
-    'CourseTicket Processor',
-    'Department Processor',
-    'Faculty Processor',
-    'Hostel Processor',
-    'Public HTML Document Processor',
-    'StudentOnlinePayment Processor',
-    'Public PDF Document Processor',
-    'Public REST Document Processor',
-    'Student Processor',
-    'StudentStudyCourse Processor (update only)',
-    'StudentStudyLevel Processor',
-    'User Processor',
-    'Verdict Processor (special processor, update only)']
-
-    >>> importerselect.getControl('Faculty Processor').selected = True
-
-    >>> modeselect = browser.getControl(name='mode')
-    >>> modeselect.options
-    ['create', 'update', 'remove']
-
-    >>> modeselect.getControl(value='create').selected = True
-    >>> browser.getControl('Proceed to step 3').click()
-
-Step 3: Fix headerlines
-
-We get informed that there are no problems with the current header:
-
-    >>> print browser.contents
-    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
-    ...
-    Header fields OK
-    ...
-
-The submit button is enabled:
-
-    >>> browser.getControl('Perform import').disabled
-    False
-
-    >>> browser.getControl('Perform import').click()
-
-Step 4: See import results
-
-The import was successful:
-
-    >>> print browser.contents
-    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
-    ...Successfully processed 3 rows...
-    ...Batch processing finished...
-    ...File:...faculties_zope.mgr.csv...
-
-We can grep the entries generated in logfile:
-
-    >>> browser.open('http://localhost/app/datacenter/logs')
-    >>> browser.getControl('Show', index=0).click()
-    >>> print browser.contents
-    <!DOCTYPE ...
-    ...<h1 class="kofa-content-label">Logfile datacenter.log</h1>...
-
-    >>> browser.getControl(name='query').value = "zope.mgr"
-    >>> browser.getControl('Search').click()
-    >>> 'zope.mgr - processed' in browser.contents
-    True
-
-
-Batch Processing Departments
-============================
-
-    >>> browser.open('http://localhost/app/datacenter')
-
-Prepare a CSV file for departments:
-
-    >>> open('departments.csv', 'wb').write(
-    ... """code,faculty_code,title,title_prefix
-    ... DEP1,FAC1,Department 1,department
-    ... DEP2,FAC2,Department 2,centre
-    ... """)
-
-Upload the file:
-
-    >>> import cStringIO
-    >>> browser.getLink('Upload data').click()
-    >>> filecontents = cStringIO.StringIO(
-    ...   open('departments.csv', 'rb').read())
-    >>> filewidget = browser.getControl(name='uploadfile:file')
-    >>> filewidget.add_file(filecontents, 'text/plain', 'departments.csv')
-    >>> browser.getControl(name='SUBMIT').click()
-
-Step 1: start batch processing:
-
-    >>> browser.getLink('Process data').click()
-    >>> button = lookup_submit_value(
-    ...   'select', 'departments_zope.mgr.csv', browser)
-    >>> button.click()
-
-Step 2: select a processor and mode:
-
-    >>> importerselect = browser.getControl(name='importer')
-    >>> importerselect.getControl('Department Processor').selected = True
-    >>> modeselect = browser.getControl(name='mode')
-    >>> modeselect.getControl(value='create').selected = True
-    >>> browser.getControl('Proceed to step 3').click()
-
-Step 3: Fix headerlines
-
-We get informed that there are no problems with the current header:
-
-    >>> print browser.contents
-    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
-    ...
-    Header fields OK
-    ...
-
-The submit button is enabled:
-
-    >>> browser.getControl('Perform import').disabled
-    False
-
-    >>> browser.getControl('Perform import').click()
-
-Step 4: See import results
-
-The import was successful:
-
-    >>> print browser.contents
-    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
-    ...Successfully processed 2 rows...
-    ...Batch processing finished...
-    ...File:...departments_zope.mgr.csv...
-
-Batch Processing Courses
-========================
-
-    >>> browser.open('http://localhost/app/datacenter')
-
-Prepare a CSV file for courses:
-
-    >>> open('courses.csv', 'wb').write(
-    ... """code,faculty_code,department_code,title,level,passmark,credits,semester
-    ... CRS1,FAC1,DEP1,Course 1,100,40,2,1
-    ... CRS2,FAC1,DEP1,Course 2,100,40,2,2
-    ... """)
-
-Upload the file:
-
-    >>> import cStringIO
-    >>> browser.getLink('Upload data').click()
-    >>> filecontents = cStringIO.StringIO(
-    ...   open('courses.csv', 'rb').read())
-    >>> filewidget = browser.getControl(name='uploadfile:file')
-    >>> filewidget.add_file(filecontents, 'text/plain', 'courses.csv')
-    >>> browser.getControl(name='SUBMIT').click()
-
-Step 1: start batch processing:
-
-    >>> browser.getLink('Process data').click()
-    >>> button = lookup_submit_value(
-    ...   'select', 'courses_zope.mgr.csv', browser)
-    >>> button.click()
-
-Step 2: select a processor and mode:
-
-    >>> importerselect = browser.getControl(name='importer')
-    >>> importerselect.getControl('Course Processor', index=1).selected = True
-    >>> modeselect = browser.getControl(name='mode')
-    >>> modeselect.getControl(value='create').selected = True
-    >>> browser.getControl('Proceed to step 3').click()
-
-Step 3: Fix headerlines
-
-We get informed that there are no problems with the current header:
-
-    >>> print browser.contents
-    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
-    ...
-    Header fields OK
-    ...
-
-The submit button is enabled:
-
-    >>> browser.getControl('Perform import').disabled
-    False
-
-    >>> browser.getControl('Perform import').click()
-
-Step 4: See import results
-
-The import was successful:
-
-    >>> print browser.contents
-    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
-    ...Successfully processed 2 rows...
-    ...Batch processing finished...
-    ...File:...courses_zope.mgr.csv...
-
-Batch Processing Certificates
-=============================
-
-    >>> browser.open('http://localhost/app/datacenter')
-
-Prepare a CSV file for certificates:
-
-    >>> open('certificates.csv', 'wb').write(
-    ... """code,faculty_code,department_code,title,study_mode,start_level,end_level,application_category
-    ... CERT1,FAC1,DEP1,Certificate 1,pg_ft,999,999,basic
-    ... CERT2,FAC1,DEP1,Certificate 2,ug_ft,200,300,cest
-    ... """)
-
-Upload the file:
-
-    >>> import cStringIO
-    >>> browser.getLink('Upload data').click()
-    >>> filecontents = cStringIO.StringIO(
-    ...   open('certificates.csv', 'rb').read())
-    >>> filewidget = browser.getControl(name='uploadfile:file')
-    >>> filewidget.add_file(filecontents, 'text/plain', 'certificates.csv')
-    >>> browser.getControl(name='SUBMIT').click()
-
-Step 1: start batch processing:
-
-    >>> browser.getLink('Process data').click()
-    >>> button = lookup_submit_value(
-    ...   'select', 'certificates_zope.mgr.csv', browser)
-    >>> button.click()
-
-
-Step 2: select a processor and mode:
-
-    >>> importerselect = browser.getControl(name='importer')
-    >>> importerselect.getControl('Certificate Processor').selected = True
-    >>> modeselect = browser.getControl(name='mode')
-    >>> modeselect.getControl(value='create').selected = True
-    >>> browser.getControl('Proceed to step 3').click()
-
-Step 3: Fix headerlines
-
-We get informed that there are no problems with the current header:
-
-    >>> print browser.contents
-    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
-    ...
-    Header fields OK
-    ...
-
-The submit button is enabled:
-
-    >>> browser.getControl('Perform import').disabled
-    False
-
-    >>> browser.getControl('Perform import').click()
-
-Step 4: See import results
-
-The import was successful:
-
-    >>> print browser.contents
-    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
-    ...Successfully processed 2 rows...
-    ...Batch processing finished...
-    ...File:...certificates_zope.mgr.csv...
-
-Batch Processing Certificate Courses
-====================================
-
-    >>> browser.open('http://localhost/app/datacenter')
-
-Prepare a CSV file for certificate courses:
-
-    >>> open('mycertcourses.csv', 'wb').write(
-    ... """course,faculty_code,department_code,certificate_code,level,mandatory
-    ... CRS1,FAC1,DEP1,CERT1,100,True
-    ... CRS2,FAC1,DEP1,CERT1,100,True
-    ... """)
-
-Upload the file:
-
-    >>> import cStringIO
-    >>> browser.getLink('Upload data').click()
-    >>> filecontents = cStringIO.StringIO(
-    ...   open('mycertcourses.csv', 'rb').read())
-    >>> filewidget = browser.getControl(name='uploadfile:file')
-    >>> filewidget.add_file(filecontents, 'text/plain', 'mycertcourses.csv')
-    >>> browser.getControl(name='SUBMIT').click()
-
-Step 1: start batch processing:
-
-    >>> browser.getLink('Process data').click()
-    >>> button = lookup_submit_value(
-    ...   'select', 'mycertcourses_zope.mgr.csv', browser)
-    >>> button.click()
-
-Step 2: select a processor and mode:
-
-    >>> importerselect = browser.getControl(name='importer')
-    >>> importerselect.getControl('CertificateCourse Processor').selected = True
-    >>> modeselect = browser.getControl(name='mode')
-    >>> modeselect.getControl(value='create').selected = True
-    >>> browser.getControl('Proceed to step 3').click()
-
-Step 3: Fix headerlines
-
-We get informed that there are no problems with the current header:
-
-    >>> print browser.contents
-    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
-    ...
-    Header fields OK
-    ...
-
-The submit button is enabled:
-
-    >>> browser.getControl('Perform import').disabled
-    False
-
-    >>> browser.getControl('Perform import').click()
-
-Step 4: See import results
-
-The import was successful:
-
-    >>> print browser.contents
-    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
-    ...Successfully processed 2 rows...
-    ...Batch processing finished...
-    ...File:...mycertcourses_zope.mgr.csv...
-
-Batch Processing Users
-======================
-
-    >>> browser.open('http://localhost/app/datacenter')
-
-Prepare a CSV file for users:
-
-    >>> open('users.csv', 'wb').write(
-    ... """name,title,public_name,email,phone,roles
-    ... uli,Uli Fouquet,Chief Developer,uli@abc.de,+49-234-567,[]
-    ... henrik,Henrik Bettermann,Admin,henrik@abc.de,+49-234-567,"['waeup.PortalManager', 'waeup.ImportManager']"
-    ... anne,Anne Palina,,anne@abc.de,+49-234-567,"['waeup.Nonsense']"
-    ... """)
-
-Upload the file:
-
-    >>> import cStringIO
-    >>> browser.getLink('Upload data').click()
-    >>> filecontents = cStringIO.StringIO(
-    ...   open('users.csv', 'rb').read())
-    >>> filewidget = browser.getControl(name='uploadfile:file')
-    >>> filewidget.add_file(filecontents, 'text/plain', 'users.csv')
-    >>> browser.getControl(name='SUBMIT').click()
-
-Step 1: start batch processing:
-
-    >>> browser.getLink('Process data').click()
-    >>> button = lookup_submit_value(
-    ...   'select', 'users_zope.mgr.csv', browser)
-    >>> button.click()
-
-Step 2: select a processor and mode:
-
-    >>> importerselect = browser.getControl(name='importer')
-    >>> importerselect.getControl('User Processor').selected = True
-    >>> modeselect = browser.getControl(name='mode')
-    >>> modeselect.getControl(value='create').selected = True
-    >>> browser.getControl('Proceed to step 3').click()
-
-Step 3: Fix headerlines
-
-We get informed that there are no problems with the current header:
-
-    >>> print browser.contents
-    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
-    ...
-    Header fields OK
-    ...
-
-The submit button is enabled:
-
-    >>> browser.getControl('Perform import').disabled
-    False
-
-    >>> browser.getControl('Perform import').click()
-
-Step 4: See import results
-
-The import was successful:
-
-    >>> print browser.contents
-    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
-    ...Successfully processed 2 rows...
-    ...Batch processing finished...
-    ...File:...users_zope.mgr.csv...
-
-User henrik has got the global roles:
-
-   >>> henrik = getRootFolder()['app']['users']['henrik']
-   >>> henrik.roles
-   ['waeup.PortalManager', 'waeup.AcademicsOfficer', 'waeup.ImportManager']
-
-Pending Files
-=============
-
-When an error occurs during an import, two files are generated: a CSV
-file with finished files and a CSV file with pending data. Both are
-stored in the appropriate subdirectories in datacenter. We try to
-create faculties, from which one already exists.
-
-Go to datacenter page:
-
-    >>> browser.open('http://localhost/app/datacenter')
-
-Prepare a CSV file for faculties:
-
-    >>> open('newfaculties.csv', 'wb').write(
-    ... """code,title,title_prefix
-    ... FAC1,Faculty 1,faculty
-    ... FAC4,Faculty 4,school
-    ... FAC 5,Faculty 5,faculty
-    ... """)
-
-Upload the file:
-
-    >>> import cStringIO
-    >>> browser.getLink('Upload data').click()
-    >>> filecontents = cStringIO.StringIO(
-    ...   open('newfaculties.csv', 'rb').read())
-    >>> filewidget = browser.getControl(name='uploadfile:file')
-    >>> filewidget.add_file(filecontents, 'text/plain', 'newfaculties.csv')
-    >>> browser.getControl(name='SUBMIT').click()
-
-Since we now have a user with waeup.ImportManager role, an email has been sent:
-    >>> print browser.contents
-    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
-    ...
-    ...All import managers have been notified by email...
-    ...
-
-Step 1: start batch processing:
-
-    >>> browser.getLink('Process data').click()
-    >>> button = lookup_submit_value(
-    ...   'select', 'newfaculties_zope.mgr.csv', browser)
-    >>> button.click()
-
-Step 2: select a processor and mode:
-
-    >>> importerselect = browser.getControl(name='importer')
-    >>> importerselect.getControl('Faculty Processor').selected = True
-    >>> modeselect = browser.getControl(name='mode')
-    >>> modeselect.getControl(value='create').selected = True
-    >>> browser.getControl('Proceed to step 3').click()
-
-Step 3: Fix headerlines
-
-As there should be no problem with the headers, we can immediately
-perfom the import:
-
-    >>> browser.getControl('Perform import').click()
-
-Two lines could not be imported:
-
-    >>> print browser.contents
-    <!DOCTYPE html PUBLIC...
-    ...
-    ...Processing of 2 rows failed...
-    ...Successfully processed 1 rows...
-    ...
-
-Now there are two files as a result in datacenter storage's root and
-``finished`` dirs:
-
-    >>> pending_file = dc_path + '/newfaculties_zope.mgr.create.pending.csv'
-    >>> print open(pending_file).read()
-    title_prefix,code,title,--ERRORS--
-    faculty,FAC1,Faculty 1,This object already exists.
-    faculty,FAC 5,Faculty 5,code: Invalid input
-
-    >>> finished_file = dc_path + '/finished/newfaculties_zope.mgr.create.finished.csv'
-    >>> print open(finished_file).read()
-    title_prefix,code,title
-    school,FAC4,Faculty 4
-
-The finished-file contains the dataset we could import, while the
-pending file contains the dataset that failed, appended by an error
-message.
-
-
-Fixing the Pending File
------------------------
-
-We 'edit' the pending file (setting code to ``FAC5`` and title
-appropriately, and removing the --ERROR-- column) and finish the
-import this way:
-
-    >>> open(dc_path + '/newfaculties_zope.mgr.create.pending.csv', 'wb').write(
-    ... """title_prefix,--IGNORE--,code,title
-    ... faculty,,FAC5,Faculty 5
-    ... """)
-
-Step 1: start batch processing:
-
-    >>> browser.open('http://localhost/app/datacenter')
-    >>> browser.getLink('Process data').click()
-    >>> button = lookup_submit_value(
-    ...   'select', 'newfaculties_zope.mgr.create.pending.csv', browser)
-    >>> button.click()
-
-Step 2: select a processor and mode:
-
-    >>> importerselect = browser.getControl(name='importer')
-    >>> importerselect.getControl('Faculty Processor').selected = True
-    >>> modeselect = browser.getControl(name='mode')
-    >>> modeselect.getControl(value='create').selected = True
-    >>> browser.getControl('Proceed to step 3').click()
-
-Step 3/4: Fix headerlines and import:
-
-As there should be no problem with the headers, we can immediately
-perfom the import:
-
-    >>> browser.getControl('Perform import').click()
-
-This time everything should work:
-
-    >>> print browser.contents
-    <!DOCTYPE html PUBLIC...
-    ...
-    ...Successfully processed 1 rows...
-    ...
-
-Oh no, we forgot Anne Palina. Her user record was not imported because
-she has a non-existent role:
-
-    >>> sorted(os.listdir(dc_path))
-    ['deleted', 'finished', 'logs', 'unfinished', 'users_zope.mgr.create.pending.csv']
-
-    >>> os.listdir(dc_path + '/unfinished')
-    ['users_zope.mgr.csv']
-
-    >>> pending_file = dc_path + '/users_zope.mgr.create.pending.csv'
-    >>> print open(pending_file).read()
-    name,roles,title,public_name,phone,email,--ERRORS--
-    anne,['waeup.Nonsense'],Anne Palina,,+49-234-567,anne@abc.de,roles: invalid role
-
-There are many finished-files:
-
-    >>> sorted(os.listdir(dc_path + '/finished'))
-    ['certificates_zope.mgr.create.finished.csv', ...,
-    'users_zope.mgr.create.finished.csv']
-
-Processed (finished) Files
-==========================
-
-    >>> browser.open('http://localhost/app/datacenter/processed')
-    >>> 'download?filename=finished/certificates_zope.mgr.create.finished.csv' in browser.contents
-    True
-
-Log Files
-=========
-
-    >>> browser.open('http://localhost/app/datacenter/logs')
-    >>> 'datacenter.log' in browser.contents
-    True
-    >>> browser.getControl('Show', index=0).click()
-    >>> browser.getControl('Back', index=0).click()
-    >>> browser.getControl('Back to Data Center').click()
-    >>> 'Storage path:' in browser.contents
-    True
-
-
-Clean up:
-
-    >>> import shutil
-    >>> shutil.rmtree(dc_path)
Index: in/waeup.kofa/trunk/src/waeup/kofa/browser/breadcrumbs.txt
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/browser/breadcrumbs.txt	(revision 12936)
+++ 	(revision )
@@ -1,243 +1,0 @@
-Breadcrumbs
-***********
-
-We use an adapter approach to get a flexible system of creating and
-retrieving breadcrumbs for certain views.
-
-
-How to get existing breadcrumbs
-===============================
-
-We create a university to check the breadcrumb functionality.
-
-    >>> from waeup.kofa.app import University
-    >>> root = getRootFolder()
-    >>> root['app'] = University()
-    >>> app = root['app']
-
-The quick way (if you have a viewname)
---------------------------------------
-
-We can get an ordered list of breadcrumbs for a given context and
-viewname:
-
-    >>> from waeup.kofa.browser.breadcrumbs import getBreadcrumbList
-    >>> blist1 = getBreadcrumbList(app, 'index')
-    >>> blist1
-    [<waeup.kofa.browser.breadcrumbs.UniversityBreadcrumb object at 0x...>]
-
-A slightly more extensive list for the datacenter:
-
-    >>> blist2 = getBreadcrumbList(app['datacenter'], 'index')
-    >>> from pprint import pprint
-    >>> pprint(blist2)
-    [<waeup.kofa.browser.breadcrumbs.UniversityBreadcrumb object at 0x...>,
-     <waeup.kofa.browser.breadcrumbs.AdministrationBreadcrumb object at 0x...>,
-     <waeup.kofa.browser.breadcrumbs.DataCenterBreadcrumb object at 0x...>]
-
-We get a breadcrumb for university, administration area and data
-center in that order.
-
-Each breadcrumb provides a ``title``, ``context``, and ``viewname``
-attribute, so that views and page templates can easily create markup
-out of it:
-
-     >>> pprint([(x.title) for x in blist2])
-     [u'Home', u'Administration', u'Data Center']
-
-     >>> pprint([(x.context, x.viewname) for x in blist2])
-     [(<waeup.kofa.app.University object at 0x...>, 'index'),
-      (<waeup.kofa.app.University object at 0x...>, 'administration'),
-      (<waeup.kofa.datacenter.DataCenter object at 0x...>, 'index')]
-
-The administration area breadcrumb might be a surprise, as there is no
-equivalent object in the ZODB. In fact the administration area is only
-a certain view (the 'administration' view) on the university object.
-
-We will show below, how you can define breadcrumbs this way.
-
-As a rule of thumb you should know, that every breadcrumb is not only
-attached for a certain kind of object, but for a tuple of context
-object and a viewname.
-
-This way we can make sure, that there are different breadcrumb lists
-generated for instance for the administration view of University
-instances and the index view. While the first should look something
-like::
-
-  Home -> Administration
-
-we expect for the latter only::
-
-  Home
-
-
-Another quick way (if you have a complete view)
------------------------------------------------
-
-Another quick approach for use in viewlets, views and similar
-contexts, where you usually have a view available but not a viewname,
-is to use ``getBreadcrumbListForView``.
-
-We create a view (a page in this case, but pages are views) on the
-user administration index page:
-
-    >>> from zope.publisher.browser import TestRequest
-    >>> from zope.component import getMultiAdapter
-    >>> page = getMultiAdapter((app['users'], TestRequest()), name='index')
-
-Now we can get the breadcrumbs for this view:
-
-    >>> from waeup.kofa.browser.breadcrumbs import getBreadcrumbListForView
-    >>> blist3 = getBreadcrumbListForView(page)
-    >>> [x.title for x in blist3]
-    [u'Home', u'Administration', u'Officers']
-
-..note:: This works only for views created with grokcore.component
-         (i.e. Grok Views, Pages, etc.) but not for simple Zope3
-         views, as the viewname can not easily be extracted from the
-         view for the latter ones.
-
-
-Type-safe retrieval of breadcrumbs for a view
----------------------------------------------
-
-If you want to make sure, that a view for which you want to get
-breadcrumbs is really a Grok view, then you can use the
-IBreadcrumbContainer adapter. This one adapts only grok views.
-
-The returned breadcrumb container supports iteration:
-
-    >>> from waeup.kofa.browser.interfaces import IBreadcrumbContainer
-    >>> mybccontainer = IBreadcrumbContainer(page)
-    >>> [x.title for x in mybccontainer]
-    [u'Home', u'Administration', u'Officers']
-
-It is, however, not a real Python list but only an iterable
-container. You can also get the real list of breadcrumbs:
-
-    >>> pprint(mybccontainer.getList())
-    [<...breadcrumbs.UniversityBreadcrumb object at 0x...>,
-     <...breadcrumbs.AdministrationBreadcrumb object at 0x...>,
-     <...breadcrumbs.UsersContainerBreadcrumb object at 0x...>]
-
-
-
-The general details
--------------------
-
-Now we can get breadcrumbs for contexts and view names. For example a
-breadcrumb for the 'index' view of our University object:
-
-    >>> from zope.component import getAdapter
-    >>> from waeup.kofa.browser.interfaces import IBreadcrumb
-    >>> b1 = getAdapter(app, IBreadcrumb, 'index')
-    >>> b1
-    <waeup.kofa.browser.breadcrumbs.UniversityBreadcrumb object at 0x...>
-
-Breadcrumb objects provide a title:
-
-    >>> b1.title
-    u'Home'
-
-a parent (None for the app object):
-
-    >>> b1.parent is None
-    True
-
-and a viewname:
-
-    >>> b1.viewname
-    'index'
-
-Using a context and a viewname, one can easily compute a complete URL
-in views, viewlets, etc.
-
-    >>> b2 = getAdapter(app['datacenter'], IBreadcrumb, 'index')
-    >>> b2
-    <waeup.kofa.browser.breadcrumbs.DataCenterBreadcrumb object at 0x...>
-
-    >>> b2.title
-    u'Data Center'
-
-    >>> b2.viewname
-    'index'
-
-As the data center is not root of the hierarchy, it provides a
-non-trivial parent:
-
-    >>> b2.parent
-    (<waeup.kofa.app.University object at 0x...>, 'administration')
-
-This result denotes a new context object (the University instance we
-created above) and a view name ('administration').
-
-Normally, the view name would be 'index', but as some administrative
-components should look to the user as if they were grouped in a common
-subarea of the root (the administration area), we can also set a
-different view than the default as target.
-
-When we like to receive the breadcrumb for this parent, we can do so
-as above:
-
-    >>> context, viewname = b2.parent
-    >>> b3 = getAdapter(context, IBreadcrumb, viewname)
-    >>> b3
-    <waeup.kofa...breadcrumbs.AdministrationBreadcrumb object at 0x...>
-
-As you can see, we get an AdministrationBreadcrumb, although the
-context object, for which the breadcrumb was created is also the
-University instance as above:
-
-    >>> b3.context is b1.context
-    True
-
-This administration area now has still a parent, the front-page:
-
-    >>> context, viewname = b3.parent
-    >>> context, viewname
-    (<waeup.kofa.app.University object at 0x...>, 'index')
-
-We create last breadcrumb:
-
-    >>> b4 = getAdapter(context, IBreadcrumb, viewname)
-
-This is finally the last piece, the breadcrumb which links to the
-front page. It has no parent:
-
-    >>> b4.parent is None
-    True
-
-
-How to create new breadcrumbs
-=============================
-
-Breadcrumbs are looked up as named adapters. This means, you must
-create a named adapter with the following specifications:
-
- * Derive your breadcrumb class from ``Breadcrumb`` class. This will
-   save you lots of work.
-
- * The adapter context (``grok.context``) must be the object type you
-   want to provide a breadcrumb for. So, if you want to write a
-   Breadcrumb adapter for objects providing ``IMyFunnyType``, write::
-
-     grok.context(IMyFunnyType)
-
- * The adapter name (``grok.name``) must be the same as the name of
-   the view, for which the breadcrumb component should be
-   registered. 
-
-   If you want to write a breadcrumb component for the 'index' view of
-   some content type and derive your class from Breadcrumb, then you
-   can skip this step as 'index' is the default name.
-
- * Set a ``title`` attribute to give the text that should be
-   displayed. Use something like::
-
-     @property
-     def title(self):
-       return self.some_computed_title()
-
-   if you want computed titles.
-
Index: in/waeup.kofa/trunk/src/waeup/kofa/browser/browser.txt
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/browser/browser.txt	(revision 12936)
+++ 	(revision )
@@ -1,1354 +1,0 @@
-Browsing Kofa
-*************
-
-Here we visit parts of a Kofa portal using a browser.
-
-University
-==========
-
-We can watch universities in the browser.
-
-We create an university object and put into the ZODB root::
-
-  >>> root = getRootFolder()
-  >>> list(root)
-  []
-
-  >>> from waeup.kofa.app import University
-  >>> u = University()
-  >>> root['myuniversity'] = u
-  >>> list(root)
-  [u'myuniversity']
-
-  >>> from zope.component.hooks import setSite
-  >>> setSite(root['myuniversity'])
-
-To make sure, we can 'watch' pages, we first have to initialize out
-test browser::
-
-  >>> from zope.testbrowser.testing import Browser
-  >>> browser = Browser()
-
-Let's get the default view of a university::
-
-  >>> browser.open('http://localhost/myuniversity')
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...Welcome to WAeUP.Kofa...
-  ...
-
-We can change to German::
-
-  >>> browser.getLink('de').click()
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...Anfragen...
-  ...
-
-And then change back to English by clicking on the third link containing 'en'
-behind 'Anfragen' and 'Einloggen'::
-
-  >>> browser.getLink('en', index=2).click()
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...Enquiries...
-  ...
-
-The contact form for anonymous users is called 'Enquiries'::
-
-  >>> browser.open('http://localhost/myuniversity')
-  >>> browser.getLink('Enquiries').click()
-  >>> browser.getControl('Send').click()
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...Required input is missing...
-  ...
-
-  >>> browser.getControl(name='form.fullname').value = "Bob Tester"
-  >>> browser.getControl(name='form.email_from').value = "xx@yy.zz"
-  >>> browser.getControl(name='form.body').value = u'test message'
-  >>> browser.getControl('Send').click()
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...Your message has been sent...
-  ...
-
-Registered users with an email address can request a password change:
-
-  >>> root['myuniversity']['users'].addUser('forgetful', 'secret',title='Bob Forgetful',
-  ...                               description='A forgetful user', email='aa@aa.ng')
-  >>> browser.open('http://localhost/myuniversity/changepw')
-  >>> browser.getControl(name="form.identifier").value = 'forgetful'
-  >>> browser.getControl(name="form.email").value = 'aa@aa.ng'
-  >>> browser.getControl("Send login credentials").click()
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...An email with your user name and password has been sent to aa@aa.ng...
-
-Now we login as manager::
-
-  >>> browser.addHeader('Authorization', 'Basic mgr:mgrpw')
-  >>> browser.handleErrors = False
-
-We can then get an edit view of the configuration container::
-
-  >>> browser.open('http://localhost/myuniversity/configuration')
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...<form action="http://localhost/myuniversity/configuration/@@index"
-  ...
-
-The edit form contains the default value for the university name::
-
-  >>> 'Sample University' in browser.contents
-  True
-
-We can perform several actions on the edit form::
-
-  >>> browser.getControl("Save", index=0).click()
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...Form has been saved...
-  ...
-
-  >>> browser.open('http://localhost/myuniversity/configuration')
-  >>> browser.getControl("Update plugins").click()
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...Plugins were updated. See log file for details...
-  ...
-
-The default frontpage HTML has been saved in a dictionary
-and is properly rendered on the frontpage of the portal:
-
-  >>> browser.open('http://localhost/myuniversity')
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...<h1>Welcome to WAeUP.Kofa...</h1>
-  ...
-
-The German part is really not being rendered:
-
-  >>> 'Willkommen' in browser.contents
-  False
-
-
-If we change to German so that the German part of frontpage.rst is rendered:
-
-  >>> browser.open('http://localhost/myuniversity//@@change_language?lang=de')
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...<h1>Willkommen auf WAeUP.Kofa...</h1>
-  ...
-
-The English part is really not being rendered:
-
-  >>> 'Welcome' in browser.contents
-  False
-
-Switch back to English:
-
-  >>> browser.open('http://localhost/myuniversity//@@change_language?lang=en')
-
-We can export a university as XML::
-
-  >>> browser.open('http://localhost/myuniversity/export.xml')
-  >>> print browser.contents
-  <?xml version="1.0" encoding="utf-8" ?>
-  <pickle>
-  ...
-  </pickle>
-
-  >>> print browser.headers
-  Status: 200 Ok
-  Content-Length: ...
-  Content-Type: text/xml; charset=UTF-8
-  X-Powered-By: Zope (www.zope.org), Python (www.python.org)
-
-
-Officers
-========
-
-  >>> browser.open('http://localhost/myuniversity')
-  >>> browser.getLink('Officers').click()
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...Officers...
-  ...
-
-We can add officers:
-
-  >>> browser.getLink("Add officer").click()
-  >>> browser.getControl(name="form.name").value = 'bob'
-  >>> browser.getControl(name="form.title").value = 'Bob The User'
-  >>> browser.getControl(name="password").value = 'secret'
-  >>> browser.getControl(name="control_password").value = 'secret'
-  >>> browser.getControl(name="form.email").value = 'xx@yy.zz'
-  >>> browser.getControl(name="form.phone.country").value = ['+234']
-  >>> browser.getControl(name="form.phone.area").value = '123'
-  >>> browser.getControl(name="form.phone.ext").value = '45678'
-  >>> browser.getControl("Add officer").click()
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...<td>bob</td>
-  ...
-
-We can edit user bob:
-
-  >>> browser.getControl("Manage", index=0).click()
-  >>> browser.getControl("Save", index=0).click()
-  >>> browser.getControl("Cancel", index=0).click()
-
-We can add site roles which are then displayed on the user container page.
-Since the test browser does not use javascript, we have to add site roles
-manually by setting the roles attribute:
-
-  >>> print root['myuniversity']['users']['bob'].roles
-  []
-  >>> root['myuniversity']['users']['bob'].roles = ['waeup.ApplicationsOfficer']
-  >>> print root['myuniversity']['users']['bob'].roles
-  ['waeup.ApplicationsOfficer']
-  >>> browser.open('http://localhost/myuniversity/users')
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...<td nowrap>Applications Officer (view only) <br /></td>
-  ...
-
-Officers can't be added twice:
-
-  >>> browser.open('http://localhost/myuniversity/users/add')
-  >>> browser.getControl(name="form.name").value = 'bob'
-  >>> browser.getControl(name="form.title").value = 'Bob The User'
-  >>> browser.getControl(name="password").value = 'secret'
-  >>> browser.getControl(name="control_password").value = 'secret'
-  >>> browser.getControl(name="form.email").value = 'xx@yy.zz'
-  >>> browser.getControl(name="form.phone.country").value = ['+234']
-  >>> browser.getControl(name="form.phone.area").value = '123'
-  >>> browser.getControl(name="form.phone.ext").value = '45678'
-  >>> browser.getControl("Add officer").click()
-  >>> 'The userid chosen already exists' in browser.contents
-  True
-
-Officers can be deleted:
-
-  >>> browser.open('http://localhost/myuniversity/users')
-  >>> browser.getControl("Remove", index=0).click()
-  >>> 'User account bob successfully deleted' in browser.contents
-  True
-
-
-
-Contact Form
-============
-
-Let's enter the contact form::
-
-  >>> browser.open('http://localhost/myuniversity/contactadmin')
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...Contact
-  ...
-
-We fill the form  (this will send a real message to
-contact@waeup.org)::
-
-  >>> browser.open('http://localhost/myuniversity/contactadmin')
-  >>> browser.getControl(name='form.body').value = "test message"
-  >>> browser.getControl('Send').click()
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...Your message has been sent...
-  ...
-
-If this test fails, chances are, that the local machine has no SMTP
-server installed.
-
-
-
-Faculties
-=========
-
-Faculties are stored in a special container of `IUniversity`
-instances. The container is called ``faculties`` and provides an
-add-form to add new faculties::
-
-  >>> browser.open('http://localhost/myuniversity/faculties/manage')
-  >>> browser.getControl('Add faculty').click()
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...*...
-  ...<span>Name of faculty</span>:
-  ...
-  >>> browser.getControl('Cancel').click()
-  >>> browser.open('http://localhost/myuniversity/faculties/add')
-
-We fill in a new name for our new faculty::
-
-  >>> ctrl = browser.getControl(name='form.title')
-  >>> ctrl.value = 'TestFac'
-
-Furthermore we add a prefix and a code (kind of abbreviation):
-
-  >>> browser.getControl(name='form.code').value = 'TF'
-
-Finally we click on 'Add Faculty' to add the new thing::
-
-  >>> browser.getControl('Add faculty').click()
-
-We can view a faculty by browsing a URL like this::
-
-  >>> browser.open('http://localhost/myuniversity/faculties/TF')
-
-Afterwards, the faculty should be visible:
-
-  >>> browser.open('http://localhost/myuniversity/faculties')
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
-  ...<h1 class="kofa-content-label">Academic Section</h1>
-  ...<td> <a href="http://localhost/myuniversity/faculties/TF"> <span>TF</span></a></td>
-  ...
-
-We can 'visit' each faculty by clicking on the appropriate link:
-
-  >>> browser.getLink('TF').click()
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...Faculty of TestFac (TF)...
-  ...
-
-If we add the same faculty twice, an error message will occur:
-
-  >>> browser.open('http://localhost/myuniversity/faculties/add')
-  >>> ctrl = browser.getControl(name='form.title')
-  >>> ctrl.value = 'TestFac'
-  >>> browser.getControl(name='form.code').value = 'TF'
-  >>> browser.getControl('Add faculty').click()
-  >>> 'The faculty code chosen already exists.' in browser.contents
-  True
-
-Modifying faculties
--------------------
-
-A faculty can directly be reached by its code:
-
-  >>> browser.open('http://localhost/myuniversity/faculties/TF')
-
-We can change the settings for a faculty by clicking on the
-provided 'Manage  faculty' button:
-
-  >>> browser.getLink('Manage faculty').click()
-
-Let's set a new title and save the form:
-
-  >>> browser.getControl(name='form.title').value = "My renamed faculty"
-  >>> browser.getControl(name='form.actions.save').click()
-
-Our faculty was indeed renamed to ``My renamed faculty``:
-
-  >>> browser.open('http://localhost/myuniversity/faculties')
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...<h1 class="kofa-content-label">Academic Section</h1>
-  ...<td> <a href="http://localhost/myuniversity/faculties/TF"> <span>TF</span></a></td>
-  ...<td>Faculty of My renamed faculty (TF)</td>
-  ...
-
-We can grant local roles:
-
-  >>> root['myuniversity']['users'].addUser('bob', 'secret',title='Bob',
-  ...                               description='A sample user')
-  >>> browser.open('http://localhost/myuniversity/faculties/TF/manage')
-  >>> browser.getControl(name="user").value = ['bob']
-  >>> browser.getControl(
-  ...     name="local_role").value = ['waeup.local.DepartmentManager']
-  >>> browser.getControl("Add local role").click()
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...<td>bob</td>
-  ...
-
-On the officers page the new local role is displayed:
-
-  >>> browser.getLink("Officers").click()
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...<td>Department Manager:...
-  ...
-
-The local role can be removed again:
-
-  >>> browser.open('http://localhost/myuniversity/faculties/TF/manage')
-  >>> ctrl = browser.getControl(name='role_id')
-  >>> browser.getControl("Remove selected local roles").click()
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...No local role selected...
-  ...
-
-  >>> browser.open('http://localhost/myuniversity/faculties/TF/manage')
-  >>> ctrl = browser.getControl(name='role_id')
-  >>> ctrl.getControl(value='bob|waeup.local.DepartmentManager').selected = True
-  >>> browser.getControl("Remove selected local roles").click()
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...Local role successfully removed...
-  ...
-
-Deleting faculties
-------------------
-
-  >>> browser.open('http://localhost/myuniversity/faculties/manage')
-  >>> browser.getControl('Cancel').click()
-  >>> print browser.url
-  http://localhost/myuniversity/faculties
-  >>> browser.getLink('Manage academic section').click()
-  >>> browser.getControl('Add faculty').click()
-  >>> ctrl = browser.getControl(name='form.title')
-  >>> ctrl.value = 'Second Faculty'
-  >>> browser.getControl(name='form.code').value = 'TF2'
-  >>> browser.getControl('Add faculty').click()
-  >>> browser.open('http://localhost/myuniversity/faculties/manage')
-  >>> browser.getControl("Remove selected", index=0).click()
-  >>> 'No item selected' in browser.contents
-  True
-  >>> browser.open('http://localhost/myuniversity/faculties/manage')
-  >>> ctrl = browser.getControl(name='val_id')
-  >>> ctrl.getControl(value='TF2').selected = True
-  >>> browser.getControl("Remove selected", index=0).click()
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...Successfully removed:...
-  ...
-
-Departments
-===========
-
-Adding departments
-------------------
-
-Departments are stored in :class:`IFaculty` instances with their code
-as key. Faculties therefore are also department containers. Faculties
-provides an add-form to add new departments:
-
-  >>> browser.open('http://localhost/myuniversity/faculties/TF/add')
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...*</span>
-  ...<span>Name of department</span>:
-  ...
-  >>> browser.getControl('Cancel').click()
-  >>> browser.open('http://localhost/myuniversity/faculties/TF/add')
-
-We fill in a new name for our new department:
-
-  >>> ctrl = browser.getControl(name='form.title')
-  >>> ctrl.value = 'TestDept'
-
-Furthermore we add a code (kind of abbreviation):
-
-  >>> browser.getControl(name='form.code').value = 'TD'
-
-Finally we click on 'Add Department' to add the new thing::
-
-  >>> browser.getControl('Add department').click()
-
-If we try to register a department under the same code twice we will
-get an error:
-
-  >>> browser.open('http://localhost/myuniversity/faculties/TF/add')
-  >>> ctrl = browser.getControl(name='form.title')
-  >>> ctrl.value = 'Another TestDept with same code'
-  >>> browser.getControl(name='form.code').value = 'TD'
-  >>> browser.getControl('Add department').click()
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...The code chosen already exists in this faculty...
-  ...
-
-We can view a department by browsing a URL like this::
-
-  >>> browser.open('http://localhost/myuniversity/faculties/TF/TD')
-
-Afterwards, the department should be visible::
-
-  >>> browser.open('http://localhost/myuniversity/faculties/TF')
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...<h1 class="kofa-content-label">Departments</h1>
-  ...<td> <a href="http://localhost/myuniversity/faculties/TF/TD"> <span>TD</span></a></td>
-  ...<td>Department of TestDept (TD)</td>
-  ...
-
-
-Modifying departments
----------------------
-
-We can change the settings for a department by clicking on the
-provided 'Edit department' button:
-
-  >>> browser.open('http://localhost/myuniversity/faculties/TF/TD')
-  >>> browser.getLink('Manage department').click()
-
-Let's set a new title and save the form:
-
-  >>> browser.getControl(name='form.title').value = "My test dept"
-  >>> browser.getControl(name='form.actions.save').click()
-
-Clicking 'Save' we will stay on the settings form. So we can change
-the department again.
-
-  >>> browser.getControl(name='form.title').value = "My renamed dept"
-  >>> ctrl = browser.getControl("Save")
-  >>> ctrl.click()
-
-
-Our department was indeed renamed to ``My renamed dept``:
-
-  >>> browser.open('http://localhost/myuniversity/faculties/TF')
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
-  ...<h1 class="kofa-content-label">Departments</h1>
-  ...<td> <a href="http://localhost/myuniversity/faculties/TF/TD"> <span>TD</span></a></td>
-  ...<td>Department of My renamed dept (TD)</td>
-  ...
-
-We can grant local roles:
-
-  >>> browser.open('http://localhost/myuniversity/faculties/TF/TD/manage')
-  >>> browser.getControl(name="user").value = ['bob']
-  >>> browser.getControl(
-  ...     name="local_role").value = ['waeup.local.DepartmentManager']
-  >>> browser.getControl("Add local role").click()
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...<td>bob</td>
-  ...
-
-The local role can be removed again:
-
-  >>> browser.open('http://localhost/myuniversity/faculties/TF/TD/manage')
-  >>> ctrl = browser.getControl(name='role_id')
-  >>> browser.getControl("Remove selected local roles").click()
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...No local role selected...
-  ...
-
-  >>> browser.open('http://localhost/myuniversity/faculties/TF/TD/manage')
-  >>> ctrl = browser.getControl(name='role_id')
-  >>> ctrl.getControl(
-  ...     value='bob|waeup.local.DepartmentManager').selected = True
-  >>> browser.getControl("Remove selected local roles").click()
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...Local role successfully removed...
-  ...
-
-Deleting departments
---------------------
-
-  >>> browser.open('http://localhost/myuniversity/faculties/TF/manage')
-  >>> browser.getControl('Cancel', index=0).click()
-  >>> print browser.url
-  http://localhost/myuniversity/faculties/TF
-  >>> browser.getLink('Manage faculty').click()
-  >>> browser.getControl('Add department').click()
-  >>> ctrl = browser.getControl(name='form.title')
-  >>> ctrl.value = 'Second Department'
-  >>> browser.getControl(name='form.code').value = 'TD2'
-  >>> browser.getControl('Add department').click()
-  >>> browser.open('http://localhost/myuniversity/faculties/TF/manage')
-  >>> browser.getControl("Remove selected", index=0).click()
-  >>> 'No item selected' in browser.contents
-  True
-  >>> browser.open('http://localhost/myuniversity/faculties/TF/manage')
-  >>> ctrl = browser.getControl(name='val_id')
-  >>> ctrl.getControl(value='TD2').selected = True
-  >>> browser.getControl("Remove selected", index=0).click()
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...Successfully removed:...
-  ...
-
-
-Courses
-=======
-
-Once we have a department, we can add courses.
-
-Adding courses
---------------
-
-Courses are stored in :class:`ICoursesContainer` instances with their
-code as key. CoursesContainers are normally availabe as `course`
-attribute of :class:`waeup.kofa.university.department.Department`
-instances.
-
-To ease the life of officers we do not require to browse the
-coursescontainers (which have a rather flat user interface), but
-provide adding of courses in department views.
-
-Each department provides a ``Add course`` action button near top.
-
-Departments provide an add-form to add new courses:
-
-  >>> dept_url = 'http://localhost/myuniversity/faculties/TF/TD'
-  >>> browser.open(dept_url + '/manage')
-  >>> browser.getControl('Add course').click()
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...*</span>
-  ...<span>Title of course</span>:
-  ...
-
-We fill in a name for our new course:
-
-  >>> ctrl = browser.getControl(name='form.title')
-  >>> ctrl.value = 'My Course 1'
-
-Furthermore we add a code (kind of abbreviation):
-
-  >>> browser.getControl(name='form.code').value = 'COURSE1'
-
-This course will take place in the the first semester, so we set the
-`semester` value to 1:
-
-  >>> ctrl = browser.getControl(name='form.semester')
-  >>> ctrl.options
-  ['1', '2', '3', '9']
-
-  >>> ctrl.displayOptions
-  ['1st Semester', '2nd Semester', 'Combined', 'N/A']
-
-  >>> ctrl.value = ['1']
-
-Finally, we create the course:
-
-  >>> browser.getControl('Add course').click()
-
-If we try to register a course under the same code twice we will
-get an error:
-
-  >>> browser.open(dept_url + '/addcourse')
-  >>> ctrl = browser.getControl(name='form.title')
-  >>> ctrl.value = 'Another course with same code'
-  >>> browser.getControl(name='form.code').value = 'COURSE1'
-  >>> browser.getControl('Add course').click()
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...A course with same code already exists:...
-  ...
-
-Our course will be linked under the code on the department page:
-
-  >>> browser.open(dept_url)
-  >>> browser.getLink('COURSE1').click()
-  >>> browser.url
-  'http://localhost/myuniversity/faculties/TF/TD/courses/COURSE1'
-
-Before we really add a course we can cancel the action and will be
-redirected to the department page:
-
-  >>> browser.open(dept_url + '/addcourse')
-  >>> browser.getControl('Cancel').click()
-  >>> browser.url
-  'http://localhost/myuniversity/faculties/TF/TD'
-
-
-Modifying courses
------------------
-
-We can change the settings for a course by clicking on the provided
-'Edit settings' link:
-
-  >>> browser.open(dept_url + '/courses/COURSE1')
-  >>> browser.getLink('Edit course').click()
-
-When modifying a course, we cannot change the code any more:
-
-  >>> browser.getControl(name='form.code')
-  Traceback (most recent call last):
-  ...
-  LookupError: name 'form.code'
-
-Let's set a new title and save the form:
-
-  >>> browser.getControl(name='form.title').value = "My test course"
-  >>> browser.getControl(name='form.actions.save').click()
-
-Clicking 'Save' we will stay on the settings form. So we can change
-the course again. If we click ``Cancel`` nothing will be
-changed:
-
-  >>> browser.getControl(name='form.title').value = "Blah"
-  >>> browser.getControl('Cancel').click()
-
-Our course was not renamed to ``Blah``:
-
-  >>> browser.open('http://localhost/myuniversity/faculties/TF/TD')
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...<td>My test course</td>...
-  ...
-
-Searching courses
------------------
-
-  >>> browser.open('http://localhost/myuniversity/faculties/search')
-  >>> browser.getControl(name='query').value = "My test course"
-  >>> browser.getControl('Search').click()
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...faculties/TF/TD/courses/COURSE1">COURSE1</a>
-  ...
-
-  >>> browser.open('http://localhost/myuniversity/faculties/search')
-  >>> browser.getControl(name='query').value = "COURSE1"
-  >>> browser.getControl('Search').click()
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...faculties/TF/TD/courses/COURSE1">COURSE1</a>
-  ...
-
-  >>> browser.getControl(name='query').value = "Nonsense"
-  >>> browser.getControl('Search').click()
-  >>> 'Search Results' in browser.contents
-  False
-  >>> '<div class="alert alert-warning">No object found.</div>' in browser.contents
-  True
-
-  >>> browser.getControl(name='query').value = ""
-  >>> browser.getControl('Search').click()
-  >>> 'Empty search string' in browser.contents
-  True
-
-We can grant local roles:
-
-  >>> browser.open('http://localhost/myuniversity/faculties/TF/TD/courses/COURSE1/manage')
-  >>> browser.getControl(name="user").value = ['bob']
-  >>> browser.getControl(
-  ...     name="local_role").value = ['waeup.local.Lecturer']
-  >>> browser.getControl("Add local role").click()
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...<td>bob</td>
-  ...
-
-On the portal user page the new local role is displayed:
-
-  >>> browser.getLink("Officers").click()
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...<td>Lecturer:...
-  ...
-
-The local role can be removed again:
-
-  >>> browser.open('http://localhost/myuniversity/faculties/TF/TD/courses/COURSE1/manage')
-  >>> ctrl = browser.getControl(name='role_id')
-  >>> browser.getControl("Remove selected local roles").click()
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...No local role selected...
-  ...
-
-  >>> browser.open('http://localhost/myuniversity/faculties/TF/TD/courses/COURSE1/manage')
-  >>> ctrl = browser.getControl(name='role_id')
-  >>> ctrl.getControl(value='bob|waeup.local.Lecturer').selected = True
-  >>> browser.getControl("Remove selected local roles").click()
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...Local role successfully removed...
-  ...
-
-Deleting courses
-----------------
-
-We can delete courses by browsing the manage page of the containing
-department and checking the appropriate select box and clicking the
-´´Remove selected´´ button.
-
-  >>> browser.open(
-  ...     'http://localhost/myuniversity/faculties/TF/TD/@@manage#tab-2')
-  >>> 'My test course' in browser.contents
-  True
-
-  >>> browser.getControl('Cancel', index=0).click()
-  >>> browser.getLink('Manage department').click()
-  >>> browser.getControl('Remove selected courses').click()
-  >>> 'No item selected' in browser.contents
-  True
-
-  >>> browser.getControl(
-  ...    name='val_id').getControl(value='COURSE1').selected = True
-  >>> browser.getControl('Remove selected courses').click()
-
-  >>> browser.open('http://localhost/myuniversity/faculties/TF/TD')
-  >>> 'My renamed course' in browser.contents
-  False
-
-
-Deleting departments with courses
----------------------------------
-
-  >>> browser.open('http://localhost/myuniversity/faculties/TF/manage')
-  >>> browser.getControl('Cancel', index=0).click()
-  >>> browser.getLink('Manage faculty').click()
-  >>> browser.getControl('Add department').click()
-  >>> ctrl = browser.getControl(name='form.title')
-  >>> ctrl.value = 'Third Department'
-  >>> browser.getControl(name='form.code').value = 'TD3'
-  >>> browser.getControl('Add department').click()
-  >>> browser.open('http://localhost/myuniversity/faculties/TF/TD3/manage')
-  >>> browser.getControl('Add course').click()
-  >>> ctrl = browser.getControl(name='form.title')
-  >>> ctrl.value = 'My Course 5'
-  >>> browser.getControl(name='form.code').value = 'COURSE5'
-  >>> browser.getControl('Add course').click()
-  >>> browser.open('http://localhost/myuniversity/faculties/TF/manage')
-  >>> ctrl = browser.getControl(name='val_id')
-  >>> ctrl.getControl(value='TD3').selected = True
-  >>> browser.getControl("Remove selected", index=0).click()
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...Successfully removed:...
-  ...
-
-Certificates
-============
-
-Once we have a department, we can add also certificats.
-
-Adding certificates
--------------------
-
-Certificates are stored in :class:`ICertificatesContainer` instances
-with their code as key. CertificatesContainers are normally availabe as
-`certificates` attribute of
-:class:`waeup.kofa.university.department.Department` instances.
-
-To ease the life of officers we do not require to browse the
-certificatescontainers (which have in fact no user interface), but
-provide adding of certificates in department views.
-
-Each department provides a ``Add certificate`` action button near top.
-
-Departments provide an add-form to add new certificates:
-
-  >>> dept_url = 'http://localhost/myuniversity/faculties/TF/TD'
-  >>> browser.open(dept_url + '/manage')
-  >>> browser.getControl('Add certificate').click()
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...*</span>
-  ...<span>Title</span>:
-  ...
-
-We fill in a name for our new cert:
-
-  >>> ctrl = browser.getControl(name='form.title')
-  >>> ctrl.value = 'My Certificate 1'
-
-Furthermore we add a code (kind of abbreviation):
-
-  >>> browser.getControl(name='form.code').value = 'CERT1'
-
-Set the remaining required fields:
-
-  >>> browser.getControl(name='form.study_mode').value = ['ug_ft']
-  >>> browser.getControl(name='form.start_level').value = ['100']
-  >>> browser.getControl(name='form.end_level').value = ['400']
-  >>> browser.getControl(name='form.application_category').value = ['basic']
-
-Finally, we create the certificate:
-
-  >>> browser.getControl('Add certificate').click()
-
-If we try to register a certificate under the same code twice we will
-get an error:
-
-  >>> browser.open(dept_url + '/addcertificate')
-  >>> ctrl = browser.getControl(name='form.title')
-  >>> ctrl.value = 'Another cert with same code'
-  >>> browser.getControl(name='form.code').value = 'CERT1'
-  >>> browser.getControl(name='form.study_mode').value = ['ug_ft']
-  >>> browser.getControl(name='form.start_level').value = ['100']
-  >>> browser.getControl(name='form.end_level').value = ['400']
-  >>> browser.getControl(name='form.application_category').value = ['basic']
-
-  >>> browser.getControl('Add certificate').click()
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...A certificate with same code already exists:...
-  ...
-
-Our certificate will be linked under the code on the department page:
-
-  >>> browser.open(dept_url)
-  >>> browser.getLink('CERT1').click()
-  >>> browser.url
-  'http://localhost/myuniversity/faculties/TF/TD/certificates/CERT1'
-
-Before we really add a certificate we can cancel the action and will be
-redirected to the department page:
-
-  >>> browser.open(dept_url + '/addcertificate')
-  >>> browser.getControl('Cancel').click()
-  >>> browser.url
-  'http://localhost/myuniversity/faculties/TF/TD'
-
-
-Modifying certificates
-----------------------
-
-We can change the settings for a cert by clicking on the provided
-'Edit certificate' link:
-
-  >>> browser.open(dept_url + '/certificates/CERT1')
-  >>> browser.getLink('Manage certificate').click()
-
-When modifying a certificate, we cannot change the code any more:
-
-  >>> browser.getControl(name='form.code')
-  Traceback (most recent call last):
-  ...
-  LookupError: name 'form.code'
-
-Let's set a new title and save the form:
-
-  >>> browser.getControl(name='form.title').value = "My test cert"
-  >>> browser.getControl(name='form.actions.save').click()
-
-Clicking 'Save' we will stay on the settings form. So we can change
-the cert again.
-
-  >>> browser.getControl(name='form.title').value = "My renamed cert"
-  >>> ctrl = browser.getControl("Save",index=0)
-  >>> ctrl.click()
-
-If we go to the settings page and click ``Cancel`` nothing will be
-changed:
-
-  >>> browser.getControl(name='form.title').value = "Blah"
-  >>> browser.getControl('Cancel',index=0).click()
-
-Our certificate was indeed renamed to ``My renamed cert`` and not to
-``Blah``:
-
-  >>> browser.open('http://localhost/myuniversity/faculties/TF/TD')
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...<td>My renamed cert</td>...
-  ...
-
-Searching certificates
-----------------------
-
-  >>> browser.open('http://localhost/myuniversity/faculties/search')
-  >>> browser.getControl(name='query').value = "My renamed cert"
-  >>> browser.getControl('Search').click()
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...faculties/TF/TD/certificates/CERT1">CERT1</a>
-  ...
-
-  >>> browser.open('http://localhost/myuniversity/faculties/search')
-  >>> browser.getControl(name='query').value = "CERT1"
-  >>> browser.getControl('Search').click()
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...faculties/TF/TD/certificates/CERT1">CERT1</a>
-  ...
-
-Deleting certificates
----------------------
-
-We can delete certificates by browsing the manage page of the
-containing department and checking the appropriate select box and
-clicking the ´´Remove selected´´ button.
-
-  >>> browser.open(
-  ...    'http://localhost/myuniversity/faculties/TF/TD/@@manage#tab-3')
-  >>> 'My renamed cert' in browser.contents
-  True
-
-  >>> browser.getControl('Remove selected certificates').click()
-  >>> 'No item selected' in browser.contents
-  True
-
-  >>> browser.getControl(name='val_id').getControl(
-  ...    value='CERT1').selected = True
-  >>> browser.getControl('Remove selected certificates').click()
-  >>> 'My renamed cert' in browser.contents
-  False
-
-
-Certificate Courses
-===================
-
-Once we have a certificate, we can add also certificate courses. These
-are referrers of courses with some extra data.
-
-Before we can work with certificate courses, we need some certificates
-and courses to be available.
-
-  >>> browser.open(dept_url + '/addcourse')
-  >>> ctrl = browser.getControl(name='form.title')
-  >>> ctrl.value = 'Another course with same code'
-  >>> browser.getControl(name='form.code').value = 'COURSE1'
-  >>> browser.getControl(name='form.title').value = 'Course 1'
-  >>> browser.getControl('Add course').click()
-
-  >>> browser.open(dept_url + '/addcourse')
-  >>> ctrl = browser.getControl(name='form.title')
-  >>> ctrl.value = 'Another course with same code'
-  >>> browser.getControl(name='form.code').value = 'COURSE2'
-  >>> browser.getControl(name='form.title').value = 'Course 2'
-  >>> browser.getControl('Add course').click()
-
-  >>> browser.open(dept_url + '/addcertificate')
-  >>> ctrl = browser.getControl(name='form.title')
-  >>> ctrl.value = 'Another cert with same code'
-  >>> browser.getControl(name='form.code').value = 'CERT1'
-  >>> browser.getControl(name='form.title').value = 'Certificate 1'
-  >>> browser.getControl(name='form.study_mode').value = ['ug_ft']
-  >>> browser.getControl(name='form.start_level').value = ['100']
-  >>> browser.getControl(name='form.end_level').value = ['400']
-  >>> browser.getControl(name='form.application_category').value = ['basic']
-  >>> browser.getControl('Add certificate').click()
-
-  >>> browser.open(dept_url + '/addcertificate')
-  >>> ctrl = browser.getControl(name='form.title')
-  >>> ctrl.value = 'Another cert with same code'
-  >>> browser.getControl(name='form.code').value = 'CERT2'
-  >>> browser.getControl(name='form.title').value = 'Certificate 2'
-  >>> browser.getControl(name='form.study_mode').value = ['ug_ft']
-  >>> browser.getControl(name='form.start_level').value = ['100']
-  >>> browser.getControl(name='form.end_level').value = ['400']
-  >>> browser.getControl(name='form.application_category').value = ['basic']
-  >>> browser.getControl('Add certificate').click()
-
-
-Adding certificate courses
---------------------------
-
-Certcourses are stored in :class:`ICertificate` instances
-with their code as key.
-
-Each certificate provides a ``Add certificate course`` action button near top.
-
-Certificates provide an add-form to add new certcourses:
-
-  >>> cert_url = dept_url + '/certificates/CERT1'
-  >>> browser.open(cert_url + '/manage')
-  >>> browser.getControl('Add certificate course').click()
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...*</span>
-  ...<span>Level</span>:
-  ...
-
-In the add-form we will get a list of available courses to select
-from. This list will contain all courses stored in the site, not only
-the ones from local department:
-
-  >>> ctrl = browser.getControl(name='form.course')
-  >>> ctrl.displayOptions
-  ['--', 'COURSE1 - Course 1', 'COURSE2 - Course 2']
-
-We select the first course and create our certificate course:
-
-  >>> ctrl.getControl('COURSE1').selected = True
-  >>> browser.getControl(name='form.level').value = ['100']
-  >>> browser.getControl('Add certificate course').click()
-
-Our certificate course will be linked on the parent certificate page:
-
-  >>> browser.open(cert_url)
-  >>> browser.getLink('COURSE1_100').click()
-  >>> browser.url
-  'http://localhost/my...sity/faculties/TF/TD/certificates/CERT1/COURSE1_100'
-
-We can't add the same certificate course twice:
-
-  >>> cert_url = dept_url + '/certificates/CERT1'
-  >>> browser.open(cert_url + '/manage')
-  >>> browser.getControl('Add certificate course').click()
-  >>> ctrl = browser.getControl(name='form.course')
-  >>> ctrl.getControl('COURSE1').selected = True
-  >>> browser.getControl(name='form.level').value = ['100']
-  >>> browser.getControl('Add certificate course').click()
-  >>> 'The chosen certificate course is already' in browser.contents
-  True
-
-When we started to add a new certificate course, we can also cancel the
-process before submitting. This will bring us back to the certificate
-page:
-
-  >>> browser.open(cert_url + '/addcertificatecourse')
-  >>> browser.getControl('Cancel').click()
-  >>> browser.url
-  'http://localhost/myuniversity/faculties/TF/TD/certificates/CERT1'
-
-
-Modifying certificate courses
------------------------------
-
-We can change the settings for a certcourse by clicking on the
-provided 'Edit certificate course' link:
-
-  >>> browser.open(cert_url + '/COURSE1_100')
-  >>> browser.getLink('Edit certificate course').click()
-
-If we just click 'Save and return' nothing will change:
-
-  >>> browser.getControl("Save and return").click()
-  >>> browser.getLink('COURSE1_100').click()
-  >>> browser.url
-  'http://localhost/myun.../TF/TD/certificates/CERT1/COURSE1_100'
-
-Let's set a new level (it was 100 before) and save the form. This will
-bring us to the certificate index page afterwards:
-
-  >>> browser.open(cert_url + '/COURSE1_100/manage')
-  >>> browser.getControl(name='form.level').value = ['200']
-  >>> browser.getControl("Save and return").click()
-
-As we changed the level, also the URL will change:
-
-  >>> browser.getLink('COURSE1_200').click()
-  >>> browser.url
-  'http://localhost/myun.../TF/TD/certificates/CERT1/COURSE1_200'
-
-If we go to the settings page and click ``Cancel`` nothing will be
-changed:
-
-  >>> browser.getLink('Edit certificate course').click()
-  >>> browser.getControl(name='form.level').value = ['400']
-  >>> browser.getControl('Cancel').click()
-
-Our certcourse provides a new level of 200 and not 400:
-
-  >>> browser.open(cert_url + '/COURSE1_200')
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
-  ...<td>Level:</td>
-  ...<td>200 (Year 2)</td>
-  ...
-
-Searching certificate courses
------------------------------
-
-  >>> browser.open('http://localhost/myuniversity/faculties/search')
-  >>> browser.getControl(name='query').value = "COURSE1"
-  >>> browser.getControl('Search').click()
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
-  ...faculties/TF/TD/certificates/CERT1/COURSE1_200">COURSE1</a>
-  ...
-
-  >>> browser.getControl(name='query').value = "*"
-  >>> browser.getControl('Search').click()
-  >>> 'Search string not allowed' in browser.contents
-  True
-
-Deleting certificate courses
-----------------------------
-
-We can delete certcourses by browsing the containing certificate manage page:
-
-  >>> browser.open(cert_url + '/manage#tab-2')
-  >>> 'COURSE1_200</a>' in browser.contents
-  True
-  >>> browser.getControl('Remove selected certificate courses').click()
-  >>> 'No item selected' in browser.contents
-  True
-  >>> browser.getControl(name='val_id').getControl(
-  ...    value='COURSE1_200').selected = True
-  >>> browser.getControl('Remove selected certificate courses').click()
-  >>> 'Successfully removed: COURSE1_200' in browser.contents
-  True
-
-  >>> 'COURSE1_200</a>' in browser.contents
-  False
-
-
-
-Data Center
-===========
-
-The data center helps us uploading files for later import or similar.
-
-  >>> browser.open('http://localhost/myuniversity')
-  >>> browser.getLink('Data Center').click()
-
-Setting the file path
----------------------
-
-A datacenter stores files in a path in filesystem. By default this is
-a directory in the sources:
-
-  >>> print browser.contents
-  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
-  ...
-  <b>Storage path:</b> <span>.../parts/test/datacenter</span>
-  ...
-
-Going to 'Settings` we can change the path:
-
-  >>> browser.getLink('Edit settings').click()
-  >>> pathsetting = browser.getControl(name='newpath')
-
-We create a directory and set it as our upload path:
-
-  >>> import os
-  >>> cwd = os.getcwd()
-  >>> uploadpath = os.path.join(cwd, 'testfiles')
-  >>> os.mkdir(uploadpath)
-  >>> pathsetting.value = uploadpath
-
-And submit the form:
-
-  >>> browser.getControl(name='save').click()
-
-We clean up the set directory path, as there might be some files be
-copied some files from installation:
-
-  >>> files = os.listdir(uploadpath)
-  >>> for filename in files:
-  ...   if os.path.isdir(os.path.join(uploadpath, filename)):
-  ...     continue
-  ...   os.unlink(os.path.join(uploadpath, filename))
-
-We also remove any existing 'accesscodes' subdir:
-
-  >>> import shutil
-  >>> for filename in files:
-  ...   if not os.path.isdir(os.path.join(uploadpath, filename)):
-  ...     continue
-  ...   if filename != 'accesscodes':
-  ...     continue
-  ...   shutil.rmtree(os.path.join(uploadpath, filename))
-
-The new upload directory is now empty, except from the logs and other
-standard dirs, which are created automatically:
-
-  >>> sorted(os.listdir(uploadpath))
-  ['deleted', 'finished', 'logs', 'unfinished']
-
-
-Uploading files
----------------
-
-Now we can upload files. Most interesting files might be CSV files,
-that can be imported lateron. We create a CSV file containing faculty
-descriptions:
-
-  >>> open('faculties.csv', 'wb').write(
-  ... """code,title,title_prefix
-  ... FA,Arts,faculty
-  ... FS,Sciences,faculty
-  ... """)
-
-Now we can upload this file. To do this, we first go to the upload
-page:
-
-  >>> browser.getLink('Upload data').click()
-
-and enter the appropriate data in the form:
-
-  >>> filewidget = browser.getControl(name='uploadfile:file')
-  >>> filewidget
-  <Control name='uploadfile:file' type='file'>
-
-A sidenote for developers: by marking the filewidget with the
-``:file`` extension, we tell Zope to handle this field as a file
-widget.
-
-  >>> import cStringIO
-  >>> filecontents = cStringIO.StringIO(
-  ...   open('faculties.csv', 'rb').read())
-  >>> filewidget.add_file(filecontents, 'text/plain', 'myfaculties.csv')
-
-  >>> browser.getControl(name='SUBMIT').click()
-
-The file was indeed uploaded, with the current userid inserted:
-
-  >>> sorted(os.listdir(uploadpath))
-  ['deleted', 'finished', 'logs', 'myfaculties_zope.mgr.csv', 'unfinished']
-
-We create and upload also a CSV file containing departments:
-
-  >>> open('departments.csv', 'wb').write(
-  ... """code,title,title_prefix,faculty_code
-  ... LIT,Literature,department,FA
-  ... SOC,Sociology,department,FA
-  ... PHY,Physics,department,FS
-  ... INF,Informatics,department,FS
-  ... MAT,Math,department,FS
-  ... """)
-
-  >>> browser.open('http://localhost/myuniversity/datacenter/upload')
-  >>> browser.getControl(name='uploadfile:file').add_file(
-  ...   cStringIO.StringIO(open('departments.csv', 'rb').read()),
-  ...   'text/plain', 'mydepartments.csv')
-  >>> browser.getControl(name='SUBMIT').click()
-
-We create and upload also a CSV file containing courses:
-
-  >>> open('courses.csv', 'wb').write(
-  ... """code,level,title,passmark,credits,semester,faculty,department
-  ... LI1,,Introduction to Literature I,40,2,1,FA,LIT
-  ... LI2,,Introduction to Literature II,40,2,2,FA,LIT
-  ... AN1,000,Analysis I,40,2,1,FS,MAT
-  ... AN2,000,Analysis II,40,2,2,FS,MAT
-  ... """)
-
-  >>> browser.open('http://localhost/myuniversity/datacenter/upload')
-  >>> browser.getControl(name='uploadfile:file').add_file(
-  ...   cStringIO.StringIO(open('courses.csv', 'rb').read()),
-  ...   'text/plain', 'mycourses.csv')
-  >>> browser.getControl(name='SUBMIT').click()
-
-We create and upload also a CSV file containing certificates:
-
-  >>> open('certificates.csv', 'wb').write(
-  ... """code,title,faculty_code,department_code,study_mode,end_level,m_prefix,start_level,application_category
-  ... LBA,BACHELOR OF LITERATURE,FA,LIT,UG,ug_ft,500,LIT,100,basic
-  ... LMA,MASTER OF LITERATURE,FA,LIT,UG,ug_pt,500,LIT,100,cest
-  ... DME,DIPLOMA OF MATH,FS,MAT,DP,dp_ft,200,DME,100,cest
-  ... """)
-
-  >>> browser.open('http://localhost/myuniversity/datacenter/upload')
-  >>> browser.getControl(name='uploadfile:file').add_file(
-  ...   cStringIO.StringIO(open('certificates.csv', 'rb').read()),
-  ...   'text/plain', 'mycertificates.csv')
-  >>> browser.getControl(name='SUBMIT').click()
-
-We create and upload also a CSV file containing certificate courses:
-
-  >>> open('certcourses.csv', 'wb').write(
-  ... """code,faculty_code,department_code,certificate_code,level,mandatory
-  ... LI1,FA,LIT,LBA,100,True
-  ... LI2,FA,LIT,LBA,200,True
-  ... """)
-
-  >>> browser.open('http://localhost/myuniversity/datacenter/upload')
-  >>> browser.getControl(name='uploadfile:file').add_file(
-  ...   cStringIO.StringIO(open('certcourses.csv', 'rb').read()),
-  ...   'text/plain', 'mycertcourses.csv')
-  >>> browser.getControl(name='SUBMIT').click()
-
-
-Importing a CSV file
---------------------
-
-The import of CSV files is described in batchprocessing.txt.
-
-
-Clean up:
-
-  >>> import os
-  >>> import shutil
-  >>> shutil.rmtree(uploadpath)
-
Index: /main/waeup.kofa/trunk/src/waeup/kofa/doctests/batchprocessing.txt
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/doctests/batchprocessing.txt	(revision 12937)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/doctests/batchprocessing.txt	(revision 12937)
@@ -0,0 +1,675 @@
+Batch Processing via Browser
+****************************
+
+Preliminaries:
+
+We define a function that looks up a form with several submit buttons
+for the one with a given value (this functionality is missing in
+zope.testbrowser):
+
+    >>> def lookup_submit_value(name, value, browser):
+    ...   """Find a button with a certain value."""
+    ...   for num in range(0, 100):
+    ...     try:
+    ...       button = browser.getControl(name=name, index=num)
+    ...       if button.value.endswith(value):
+    ...         return button
+    ...     except IndexError:
+    ...       break
+    ...   return None
+
+Create a site:
+
+    >>> from waeup.kofa.app import University
+    >>> getRootFolder()['app'] = University()
+    >>> from zope.component.hooks import setSite
+    >>> setSite(getRootFolder()['app'])
+
+Create a datacenter storage path:
+
+    >>> import os
+    >>> import tempfile
+    >>> dc_path = tempfile.mkdtemp()
+
+Log in:
+
+    >>> from zope.testbrowser.testing import Browser
+    >>> browser = Browser()
+    >>> browser.addHeader('Authorization', 'Basic mgr:mgrpw')
+    >>> browser.handleErrors = False
+
+Set datacenter path and deselect moving old data:
+
+    >>> browser.open('http://localhost/app')
+    >>> browser.getLink('Data Center').click()
+    >>> browser.getLink('Edit settings').click()
+    >>> browser.getControl(name='newpath').value = dc_path
+    >>> browser.getControl(name='move').value = False
+    >>> browser.getControl(name='save').click()
+
+Set non-usable datacenter path:
+
+    >>> browser.getLink('Edit settings').click()
+    >>> browser.getControl(name='newpath').value = '/'
+    >>> browser.getControl(name='save').click()
+    >>> 'Given storage path cannot be used.' in browser.contents
+    True
+    >>> browser.getControl('Back to Data Center').click()
+
+
+Batch Processing Faculties
+==========================
+
+Go to datacenter page:
+
+    >>> browser.open('http://localhost/app/datacenter')
+
+Prepare a CSV file for faculties (extended ascii values are accepted):
+
+    >>> open('faculties.csv', 'wb').write(
+    ... """code,title,title_prefix
+    ... FAC1,Faculty 1,faculty
+    ... FAC2,Faculty 2,institute
+    ... FAC3,Fäcülty 3,school
+    ... """)
+
+Upload the file:
+
+    >>> import cStringIO
+    >>> browser.getLink('Upload data').click()
+    >>> filecontents = cStringIO.StringIO(
+    ...   open('faculties.csv', 'rb').read())
+    >>> filewidget = browser.getControl(name='uploadfile:file')
+    >>> filewidget.add_file(filecontents, 'text/plain', 'faculties.csv')
+    >>> browser.getControl(name='SUBMIT').click()
+
+Step 1: start batch processing:
+
+    >>> browser.getLink('Process data').click()
+    >>> button = lookup_submit_value(
+    ...   'select', 'faculties_zope.mgr.csv', browser)
+    >>> button.click()
+
+Step 2: select a processor and mode:
+
+    >>> importerselect = browser.getControl(name='importer')
+    >>> importerselect.displayOptions
+    ['AccessCodeBatch Processor',
+    'AccessCode Processor',
+    'Applicant Processor',
+    'ApplicantsContainer Processor',
+    'CertificateCourse Processor',
+    'Certificate Processor',
+    'Course Processor',
+    'CourseTicket Processor',
+    'Department Processor',
+    'Faculty Processor',
+    'Hostel Processor',
+    'Public HTML Document Processor',
+    'StudentOnlinePayment Processor',
+    'Public PDF Document Processor',
+    'Public REST Document Processor',
+    'Student Processor',
+    'StudentStudyCourse Processor (update only)',
+    'StudentStudyLevel Processor',
+    'User Processor',
+    'Verdict Processor (special processor, update only)']
+
+    >>> importerselect.getControl('Faculty Processor').selected = True
+
+    >>> modeselect = browser.getControl(name='mode')
+    >>> modeselect.options
+    ['create', 'update', 'remove']
+
+    >>> modeselect.getControl(value='create').selected = True
+    >>> browser.getControl('Proceed to step 3').click()
+
+Step 3: Fix headerlines
+
+We get informed that there are no problems with the current header:
+
+    >>> print browser.contents
+    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+    ...
+    Header fields OK
+    ...
+
+The submit button is enabled:
+
+    >>> browser.getControl('Perform import').disabled
+    False
+
+    >>> browser.getControl('Perform import').click()
+
+Step 4: See import results
+
+The import was successful:
+
+    >>> print browser.contents
+    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+    ...Successfully processed 3 rows...
+    ...Batch processing finished...
+    ...File:...faculties_zope.mgr.csv...
+
+We can grep the entries generated in logfile:
+
+    >>> browser.open('http://localhost/app/datacenter/logs')
+    >>> browser.getControl('Show', index=0).click()
+    >>> print browser.contents
+    <!DOCTYPE ...
+    ...<h1 class="kofa-content-label">Logfile datacenter.log</h1>...
+
+    >>> browser.getControl(name='query').value = "zope.mgr"
+    >>> browser.getControl('Search').click()
+    >>> 'zope.mgr - processed' in browser.contents
+    True
+
+
+Batch Processing Departments
+============================
+
+    >>> browser.open('http://localhost/app/datacenter')
+
+Prepare a CSV file for departments:
+
+    >>> open('departments.csv', 'wb').write(
+    ... """code,faculty_code,title,title_prefix
+    ... DEP1,FAC1,Department 1,department
+    ... DEP2,FAC2,Department 2,centre
+    ... """)
+
+Upload the file:
+
+    >>> import cStringIO
+    >>> browser.getLink('Upload data').click()
+    >>> filecontents = cStringIO.StringIO(
+    ...   open('departments.csv', 'rb').read())
+    >>> filewidget = browser.getControl(name='uploadfile:file')
+    >>> filewidget.add_file(filecontents, 'text/plain', 'departments.csv')
+    >>> browser.getControl(name='SUBMIT').click()
+
+Step 1: start batch processing:
+
+    >>> browser.getLink('Process data').click()
+    >>> button = lookup_submit_value(
+    ...   'select', 'departments_zope.mgr.csv', browser)
+    >>> button.click()
+
+Step 2: select a processor and mode:
+
+    >>> importerselect = browser.getControl(name='importer')
+    >>> importerselect.getControl('Department Processor').selected = True
+    >>> modeselect = browser.getControl(name='mode')
+    >>> modeselect.getControl(value='create').selected = True
+    >>> browser.getControl('Proceed to step 3').click()
+
+Step 3: Fix headerlines
+
+We get informed that there are no problems with the current header:
+
+    >>> print browser.contents
+    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+    ...
+    Header fields OK
+    ...
+
+The submit button is enabled:
+
+    >>> browser.getControl('Perform import').disabled
+    False
+
+    >>> browser.getControl('Perform import').click()
+
+Step 4: See import results
+
+The import was successful:
+
+    >>> print browser.contents
+    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+    ...Successfully processed 2 rows...
+    ...Batch processing finished...
+    ...File:...departments_zope.mgr.csv...
+
+Batch Processing Courses
+========================
+
+    >>> browser.open('http://localhost/app/datacenter')
+
+Prepare a CSV file for courses:
+
+    >>> open('courses.csv', 'wb').write(
+    ... """code,faculty_code,department_code,title,level,passmark,credits,semester
+    ... CRS1,FAC1,DEP1,Course 1,100,40,2,1
+    ... CRS2,FAC1,DEP1,Course 2,100,40,2,2
+    ... """)
+
+Upload the file:
+
+    >>> import cStringIO
+    >>> browser.getLink('Upload data').click()
+    >>> filecontents = cStringIO.StringIO(
+    ...   open('courses.csv', 'rb').read())
+    >>> filewidget = browser.getControl(name='uploadfile:file')
+    >>> filewidget.add_file(filecontents, 'text/plain', 'courses.csv')
+    >>> browser.getControl(name='SUBMIT').click()
+
+Step 1: start batch processing:
+
+    >>> browser.getLink('Process data').click()
+    >>> button = lookup_submit_value(
+    ...   'select', 'courses_zope.mgr.csv', browser)
+    >>> button.click()
+
+Step 2: select a processor and mode:
+
+    >>> importerselect = browser.getControl(name='importer')
+    >>> importerselect.getControl('Course Processor', index=1).selected = True
+    >>> modeselect = browser.getControl(name='mode')
+    >>> modeselect.getControl(value='create').selected = True
+    >>> browser.getControl('Proceed to step 3').click()
+
+Step 3: Fix headerlines
+
+We get informed that there are no problems with the current header:
+
+    >>> print browser.contents
+    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+    ...
+    Header fields OK
+    ...
+
+The submit button is enabled:
+
+    >>> browser.getControl('Perform import').disabled
+    False
+
+    >>> browser.getControl('Perform import').click()
+
+Step 4: See import results
+
+The import was successful:
+
+    >>> print browser.contents
+    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+    ...Successfully processed 2 rows...
+    ...Batch processing finished...
+    ...File:...courses_zope.mgr.csv...
+
+Batch Processing Certificates
+=============================
+
+    >>> browser.open('http://localhost/app/datacenter')
+
+Prepare a CSV file for certificates:
+
+    >>> open('certificates.csv', 'wb').write(
+    ... """code,faculty_code,department_code,title,study_mode,start_level,end_level,application_category
+    ... CERT1,FAC1,DEP1,Certificate 1,pg_ft,999,999,basic
+    ... CERT2,FAC1,DEP1,Certificate 2,ug_ft,200,300,cest
+    ... """)
+
+Upload the file:
+
+    >>> import cStringIO
+    >>> browser.getLink('Upload data').click()
+    >>> filecontents = cStringIO.StringIO(
+    ...   open('certificates.csv', 'rb').read())
+    >>> filewidget = browser.getControl(name='uploadfile:file')
+    >>> filewidget.add_file(filecontents, 'text/plain', 'certificates.csv')
+    >>> browser.getControl(name='SUBMIT').click()
+
+Step 1: start batch processing:
+
+    >>> browser.getLink('Process data').click()
+    >>> button = lookup_submit_value(
+    ...   'select', 'certificates_zope.mgr.csv', browser)
+    >>> button.click()
+
+
+Step 2: select a processor and mode:
+
+    >>> importerselect = browser.getControl(name='importer')
+    >>> importerselect.getControl('Certificate Processor').selected = True
+    >>> modeselect = browser.getControl(name='mode')
+    >>> modeselect.getControl(value='create').selected = True
+    >>> browser.getControl('Proceed to step 3').click()
+
+Step 3: Fix headerlines
+
+We get informed that there are no problems with the current header:
+
+    >>> print browser.contents
+    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+    ...
+    Header fields OK
+    ...
+
+The submit button is enabled:
+
+    >>> browser.getControl('Perform import').disabled
+    False
+
+    >>> browser.getControl('Perform import').click()
+
+Step 4: See import results
+
+The import was successful:
+
+    >>> print browser.contents
+    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+    ...Successfully processed 2 rows...
+    ...Batch processing finished...
+    ...File:...certificates_zope.mgr.csv...
+
+Batch Processing Certificate Courses
+====================================
+
+    >>> browser.open('http://localhost/app/datacenter')
+
+Prepare a CSV file for certificate courses:
+
+    >>> open('mycertcourses.csv', 'wb').write(
+    ... """course,faculty_code,department_code,certificate_code,level,mandatory
+    ... CRS1,FAC1,DEP1,CERT1,100,True
+    ... CRS2,FAC1,DEP1,CERT1,100,True
+    ... """)
+
+Upload the file:
+
+    >>> import cStringIO
+    >>> browser.getLink('Upload data').click()
+    >>> filecontents = cStringIO.StringIO(
+    ...   open('mycertcourses.csv', 'rb').read())
+    >>> filewidget = browser.getControl(name='uploadfile:file')
+    >>> filewidget.add_file(filecontents, 'text/plain', 'mycertcourses.csv')
+    >>> browser.getControl(name='SUBMIT').click()
+
+Step 1: start batch processing:
+
+    >>> browser.getLink('Process data').click()
+    >>> button = lookup_submit_value(
+    ...   'select', 'mycertcourses_zope.mgr.csv', browser)
+    >>> button.click()
+
+Step 2: select a processor and mode:
+
+    >>> importerselect = browser.getControl(name='importer')
+    >>> importerselect.getControl('CertificateCourse Processor').selected = True
+    >>> modeselect = browser.getControl(name='mode')
+    >>> modeselect.getControl(value='create').selected = True
+    >>> browser.getControl('Proceed to step 3').click()
+
+Step 3: Fix headerlines
+
+We get informed that there are no problems with the current header:
+
+    >>> print browser.contents
+    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+    ...
+    Header fields OK
+    ...
+
+The submit button is enabled:
+
+    >>> browser.getControl('Perform import').disabled
+    False
+
+    >>> browser.getControl('Perform import').click()
+
+Step 4: See import results
+
+The import was successful:
+
+    >>> print browser.contents
+    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+    ...Successfully processed 2 rows...
+    ...Batch processing finished...
+    ...File:...mycertcourses_zope.mgr.csv...
+
+Batch Processing Users
+======================
+
+    >>> browser.open('http://localhost/app/datacenter')
+
+Prepare a CSV file for users:
+
+    >>> open('users.csv', 'wb').write(
+    ... """name,title,public_name,email,phone,roles
+    ... uli,Uli Fouquet,Chief Developer,uli@abc.de,+49-234-567,[]
+    ... henrik,Henrik Bettermann,Admin,henrik@abc.de,+49-234-567,"['waeup.PortalManager', 'waeup.ImportManager']"
+    ... anne,Anne Palina,,anne@abc.de,+49-234-567,"['waeup.Nonsense']"
+    ... """)
+
+Upload the file:
+
+    >>> import cStringIO
+    >>> browser.getLink('Upload data').click()
+    >>> filecontents = cStringIO.StringIO(
+    ...   open('users.csv', 'rb').read())
+    >>> filewidget = browser.getControl(name='uploadfile:file')
+    >>> filewidget.add_file(filecontents, 'text/plain', 'users.csv')
+    >>> browser.getControl(name='SUBMIT').click()
+
+Step 1: start batch processing:
+
+    >>> browser.getLink('Process data').click()
+    >>> button = lookup_submit_value(
+    ...   'select', 'users_zope.mgr.csv', browser)
+    >>> button.click()
+
+Step 2: select a processor and mode:
+
+    >>> importerselect = browser.getControl(name='importer')
+    >>> importerselect.getControl('User Processor').selected = True
+    >>> modeselect = browser.getControl(name='mode')
+    >>> modeselect.getControl(value='create').selected = True
+    >>> browser.getControl('Proceed to step 3').click()
+
+Step 3: Fix headerlines
+
+We get informed that there are no problems with the current header:
+
+    >>> print browser.contents
+    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+    ...
+    Header fields OK
+    ...
+
+The submit button is enabled:
+
+    >>> browser.getControl('Perform import').disabled
+    False
+
+    >>> browser.getControl('Perform import').click()
+
+Step 4: See import results
+
+The import was successful:
+
+    >>> print browser.contents
+    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+    ...Successfully processed 2 rows...
+    ...Batch processing finished...
+    ...File:...users_zope.mgr.csv...
+
+User henrik has got the global roles:
+
+   >>> henrik = getRootFolder()['app']['users']['henrik']
+   >>> henrik.roles
+   ['waeup.PortalManager', 'waeup.AcademicsOfficer', 'waeup.ImportManager']
+
+Pending Files
+=============
+
+When an error occurs during an import, two files are generated: a CSV
+file with finished files and a CSV file with pending data. Both are
+stored in the appropriate subdirectories in datacenter. We try to
+create faculties, from which one already exists.
+
+Go to datacenter page:
+
+    >>> browser.open('http://localhost/app/datacenter')
+
+Prepare a CSV file for faculties:
+
+    >>> open('newfaculties.csv', 'wb').write(
+    ... """code,title,title_prefix
+    ... FAC1,Faculty 1,faculty
+    ... FAC4,Faculty 4,school
+    ... FAC 5,Faculty 5,faculty
+    ... """)
+
+Upload the file:
+
+    >>> import cStringIO
+    >>> browser.getLink('Upload data').click()
+    >>> filecontents = cStringIO.StringIO(
+    ...   open('newfaculties.csv', 'rb').read())
+    >>> filewidget = browser.getControl(name='uploadfile:file')
+    >>> filewidget.add_file(filecontents, 'text/plain', 'newfaculties.csv')
+    >>> browser.getControl(name='SUBMIT').click()
+
+Since we now have a user with waeup.ImportManager role, an email has been sent:
+    >>> print browser.contents
+    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+    ...
+    ...All import managers have been notified by email...
+    ...
+
+Step 1: start batch processing:
+
+    >>> browser.getLink('Process data').click()
+    >>> button = lookup_submit_value(
+    ...   'select', 'newfaculties_zope.mgr.csv', browser)
+    >>> button.click()
+
+Step 2: select a processor and mode:
+
+    >>> importerselect = browser.getControl(name='importer')
+    >>> importerselect.getControl('Faculty Processor').selected = True
+    >>> modeselect = browser.getControl(name='mode')
+    >>> modeselect.getControl(value='create').selected = True
+    >>> browser.getControl('Proceed to step 3').click()
+
+Step 3: Fix headerlines
+
+As there should be no problem with the headers, we can immediately
+perfom the import:
+
+    >>> browser.getControl('Perform import').click()
+
+Two lines could not be imported:
+
+    >>> print browser.contents
+    <!DOCTYPE html PUBLIC...
+    ...
+    ...Processing of 2 rows failed...
+    ...Successfully processed 1 rows...
+    ...
+
+Now there are two files as a result in datacenter storage's root and
+``finished`` dirs:
+
+    >>> pending_file = dc_path + '/newfaculties_zope.mgr.create.pending.csv'
+    >>> print open(pending_file).read()
+    title_prefix,code,title,--ERRORS--
+    faculty,FAC1,Faculty 1,This object already exists.
+    faculty,FAC 5,Faculty 5,code: Invalid input
+
+    >>> finished_file = dc_path + '/finished/newfaculties_zope.mgr.create.finished.csv'
+    >>> print open(finished_file).read()
+    title_prefix,code,title
+    school,FAC4,Faculty 4
+
+The finished-file contains the dataset we could import, while the
+pending file contains the dataset that failed, appended by an error
+message.
+
+
+Fixing the Pending File
+-----------------------
+
+We 'edit' the pending file (setting code to ``FAC5`` and title
+appropriately, and removing the --ERROR-- column) and finish the
+import this way:
+
+    >>> open(dc_path + '/newfaculties_zope.mgr.create.pending.csv', 'wb').write(
+    ... """title_prefix,--IGNORE--,code,title
+    ... faculty,,FAC5,Faculty 5
+    ... """)
+
+Step 1: start batch processing:
+
+    >>> browser.open('http://localhost/app/datacenter')
+    >>> browser.getLink('Process data').click()
+    >>> button = lookup_submit_value(
+    ...   'select', 'newfaculties_zope.mgr.create.pending.csv', browser)
+    >>> button.click()
+
+Step 2: select a processor and mode:
+
+    >>> importerselect = browser.getControl(name='importer')
+    >>> importerselect.getControl('Faculty Processor').selected = True
+    >>> modeselect = browser.getControl(name='mode')
+    >>> modeselect.getControl(value='create').selected = True
+    >>> browser.getControl('Proceed to step 3').click()
+
+Step 3/4: Fix headerlines and import:
+
+As there should be no problem with the headers, we can immediately
+perfom the import:
+
+    >>> browser.getControl('Perform import').click()
+
+This time everything should work:
+
+    >>> print browser.contents
+    <!DOCTYPE html PUBLIC...
+    ...
+    ...Successfully processed 1 rows...
+    ...
+
+Oh no, we forgot Anne Palina. Her user record was not imported because
+she has a non-existent role:
+
+    >>> sorted(os.listdir(dc_path))
+    ['deleted', 'finished', 'logs', 'unfinished', 'users_zope.mgr.create.pending.csv']
+
+    >>> os.listdir(dc_path + '/unfinished')
+    ['users_zope.mgr.csv']
+
+    >>> pending_file = dc_path + '/users_zope.mgr.create.pending.csv'
+    >>> print open(pending_file).read()
+    name,roles,title,public_name,phone,email,--ERRORS--
+    anne,['waeup.Nonsense'],Anne Palina,,+49-234-567,anne@abc.de,roles: invalid role
+
+There are many finished-files:
+
+    >>> sorted(os.listdir(dc_path + '/finished'))
+    ['certificates_zope.mgr.create.finished.csv', ...,
+    'users_zope.mgr.create.finished.csv']
+
+Processed (finished) Files
+==========================
+
+    >>> browser.open('http://localhost/app/datacenter/processed')
+    >>> 'download?filename=finished/certificates_zope.mgr.create.finished.csv' in browser.contents
+    True
+
+Log Files
+=========
+
+    >>> browser.open('http://localhost/app/datacenter/logs')
+    >>> 'datacenter.log' in browser.contents
+    True
+    >>> browser.getControl('Show', index=0).click()
+    >>> browser.getControl('Back', index=0).click()
+    >>> browser.getControl('Back to Data Center').click()
+    >>> 'Storage path:' in browser.contents
+    True
+
+
+Clean up:
+
+    >>> import shutil
+    >>> shutil.rmtree(dc_path)
Index: /main/waeup.kofa/trunk/src/waeup/kofa/doctests/breadcrumbs.txt
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/doctests/breadcrumbs.txt	(revision 12937)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/doctests/breadcrumbs.txt	(revision 12937)
@@ -0,0 +1,243 @@
+Breadcrumbs
+***********
+
+We use an adapter approach to get a flexible system of creating and
+retrieving breadcrumbs for certain views.
+
+
+How to get existing breadcrumbs
+===============================
+
+We create a university to check the breadcrumb functionality.
+
+    >>> from waeup.kofa.app import University
+    >>> root = getRootFolder()
+    >>> root['app'] = University()
+    >>> app = root['app']
+
+The quick way (if you have a viewname)
+--------------------------------------
+
+We can get an ordered list of breadcrumbs for a given context and
+viewname:
+
+    >>> from waeup.kofa.browser.breadcrumbs import getBreadcrumbList
+    >>> blist1 = getBreadcrumbList(app, 'index')
+    >>> blist1
+    [<waeup.kofa.browser.breadcrumbs.UniversityBreadcrumb object at 0x...>]
+
+A slightly more extensive list for the datacenter:
+
+    >>> blist2 = getBreadcrumbList(app['datacenter'], 'index')
+    >>> from pprint import pprint
+    >>> pprint(blist2)
+    [<waeup.kofa.browser.breadcrumbs.UniversityBreadcrumb object at 0x...>,
+     <waeup.kofa.browser.breadcrumbs.AdministrationBreadcrumb object at 0x...>,
+     <waeup.kofa.browser.breadcrumbs.DataCenterBreadcrumb object at 0x...>]
+
+We get a breadcrumb for university, administration area and data
+center in that order.
+
+Each breadcrumb provides a ``title``, ``context``, and ``viewname``
+attribute, so that views and page templates can easily create markup
+out of it:
+
+     >>> pprint([(x.title) for x in blist2])
+     [u'Home', u'Administration', u'Data Center']
+
+     >>> pprint([(x.context, x.viewname) for x in blist2])
+     [(<waeup.kofa.app.University object at 0x...>, 'index'),
+      (<waeup.kofa.app.University object at 0x...>, 'administration'),
+      (<waeup.kofa.datacenter.DataCenter object at 0x...>, 'index')]
+
+The administration area breadcrumb might be a surprise, as there is no
+equivalent object in the ZODB. In fact the administration area is only
+a certain view (the 'administration' view) on the university object.
+
+We will show below, how you can define breadcrumbs this way.
+
+As a rule of thumb you should know, that every breadcrumb is not only
+attached for a certain kind of object, but for a tuple of context
+object and a viewname.
+
+This way we can make sure, that there are different breadcrumb lists
+generated for instance for the administration view of University
+instances and the index view. While the first should look something
+like::
+
+  Home -> Administration
+
+we expect for the latter only::
+
+  Home
+
+
+Another quick way (if you have a complete view)
+-----------------------------------------------
+
+Another quick approach for use in viewlets, views and similar
+contexts, where you usually have a view available but not a viewname,
+is to use ``getBreadcrumbListForView``.
+
+We create a view (a page in this case, but pages are views) on the
+user administration index page:
+
+    >>> from zope.publisher.browser import TestRequest
+    >>> from zope.component import getMultiAdapter
+    >>> page = getMultiAdapter((app['users'], TestRequest()), name='index')
+
+Now we can get the breadcrumbs for this view:
+
+    >>> from waeup.kofa.browser.breadcrumbs import getBreadcrumbListForView
+    >>> blist3 = getBreadcrumbListForView(page)
+    >>> [x.title for x in blist3]
+    [u'Home', u'Administration', u'Officers']
+
+..note:: This works only for views created with grokcore.component
+         (i.e. Grok Views, Pages, etc.) but not for simple Zope3
+         views, as the viewname can not easily be extracted from the
+         view for the latter ones.
+
+
+Type-safe retrieval of breadcrumbs for a view
+---------------------------------------------
+
+If you want to make sure, that a view for which you want to get
+breadcrumbs is really a Grok view, then you can use the
+IBreadcrumbContainer adapter. This one adapts only grok views.
+
+The returned breadcrumb container supports iteration:
+
+    >>> from waeup.kofa.browser.interfaces import IBreadcrumbContainer
+    >>> mybccontainer = IBreadcrumbContainer(page)
+    >>> [x.title for x in mybccontainer]
+    [u'Home', u'Administration', u'Officers']
+
+It is, however, not a real Python list but only an iterable
+container. You can also get the real list of breadcrumbs:
+
+    >>> pprint(mybccontainer.getList())
+    [<...breadcrumbs.UniversityBreadcrumb object at 0x...>,
+     <...breadcrumbs.AdministrationBreadcrumb object at 0x...>,
+     <...breadcrumbs.UsersContainerBreadcrumb object at 0x...>]
+
+
+
+The general details
+-------------------
+
+Now we can get breadcrumbs for contexts and view names. For example a
+breadcrumb for the 'index' view of our University object:
+
+    >>> from zope.component import getAdapter
+    >>> from waeup.kofa.browser.interfaces import IBreadcrumb
+    >>> b1 = getAdapter(app, IBreadcrumb, 'index')
+    >>> b1
+    <waeup.kofa.browser.breadcrumbs.UniversityBreadcrumb object at 0x...>
+
+Breadcrumb objects provide a title:
+
+    >>> b1.title
+    u'Home'
+
+a parent (None for the app object):
+
+    >>> b1.parent is None
+    True
+
+and a viewname:
+
+    >>> b1.viewname
+    'index'
+
+Using a context and a viewname, one can easily compute a complete URL
+in views, viewlets, etc.
+
+    >>> b2 = getAdapter(app['datacenter'], IBreadcrumb, 'index')
+    >>> b2
+    <waeup.kofa.browser.breadcrumbs.DataCenterBreadcrumb object at 0x...>
+
+    >>> b2.title
+    u'Data Center'
+
+    >>> b2.viewname
+    'index'
+
+As the data center is not root of the hierarchy, it provides a
+non-trivial parent:
+
+    >>> b2.parent
+    (<waeup.kofa.app.University object at 0x...>, 'administration')
+
+This result denotes a new context object (the University instance we
+created above) and a view name ('administration').
+
+Normally, the view name would be 'index', but as some administrative
+components should look to the user as if they were grouped in a common
+subarea of the root (the administration area), we can also set a
+different view than the default as target.
+
+When we like to receive the breadcrumb for this parent, we can do so
+as above:
+
+    >>> context, viewname = b2.parent
+    >>> b3 = getAdapter(context, IBreadcrumb, viewname)
+    >>> b3
+    <waeup.kofa...breadcrumbs.AdministrationBreadcrumb object at 0x...>
+
+As you can see, we get an AdministrationBreadcrumb, although the
+context object, for which the breadcrumb was created is also the
+University instance as above:
+
+    >>> b3.context is b1.context
+    True
+
+This administration area now has still a parent, the front-page:
+
+    >>> context, viewname = b3.parent
+    >>> context, viewname
+    (<waeup.kofa.app.University object at 0x...>, 'index')
+
+We create last breadcrumb:
+
+    >>> b4 = getAdapter(context, IBreadcrumb, viewname)
+
+This is finally the last piece, the breadcrumb which links to the
+front page. It has no parent:
+
+    >>> b4.parent is None
+    True
+
+
+How to create new breadcrumbs
+=============================
+
+Breadcrumbs are looked up as named adapters. This means, you must
+create a named adapter with the following specifications:
+
+ * Derive your breadcrumb class from ``Breadcrumb`` class. This will
+   save you lots of work.
+
+ * The adapter context (``grok.context``) must be the object type you
+   want to provide a breadcrumb for. So, if you want to write a
+   Breadcrumb adapter for objects providing ``IMyFunnyType``, write::
+
+     grok.context(IMyFunnyType)
+
+ * The adapter name (``grok.name``) must be the same as the name of
+   the view, for which the breadcrumb component should be
+   registered. 
+
+   If you want to write a breadcrumb component for the 'index' view of
+   some content type and derive your class from Breadcrumb, then you
+   can skip this step as 'index' is the default name.
+
+ * Set a ``title`` attribute to give the text that should be
+   displayed. Use something like::
+
+     @property
+     def title(self):
+       return self.some_computed_title()
+
+   if you want computed titles.
+
Index: /main/waeup.kofa/trunk/src/waeup/kofa/doctests/browser.txt
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/doctests/browser.txt	(revision 12937)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/doctests/browser.txt	(revision 12937)
@@ -0,0 +1,1354 @@
+Browsing Kofa
+*************
+
+Here we visit parts of a Kofa portal using a browser.
+
+University
+==========
+
+We can watch universities in the browser.
+
+We create an university object and put into the ZODB root::
+
+  >>> root = getRootFolder()
+  >>> list(root)
+  []
+
+  >>> from waeup.kofa.app import University
+  >>> u = University()
+  >>> root['myuniversity'] = u
+  >>> list(root)
+  [u'myuniversity']
+
+  >>> from zope.component.hooks import setSite
+  >>> setSite(root['myuniversity'])
+
+To make sure, we can 'watch' pages, we first have to initialize out
+test browser::
+
+  >>> from zope.testbrowser.testing import Browser
+  >>> browser = Browser()
+
+Let's get the default view of a university::
+
+  >>> browser.open('http://localhost/myuniversity')
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...Welcome to WAeUP.Kofa...
+  ...
+
+We can change to German::
+
+  >>> browser.getLink('de').click()
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...Anfragen...
+  ...
+
+And then change back to English by clicking on the third link containing 'en'
+behind 'Anfragen' and 'Einloggen'::
+
+  >>> browser.getLink('en', index=2).click()
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...Enquiries...
+  ...
+
+The contact form for anonymous users is called 'Enquiries'::
+
+  >>> browser.open('http://localhost/myuniversity')
+  >>> browser.getLink('Enquiries').click()
+  >>> browser.getControl('Send').click()
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...Required input is missing...
+  ...
+
+  >>> browser.getControl(name='form.fullname').value = "Bob Tester"
+  >>> browser.getControl(name='form.email_from').value = "xx@yy.zz"
+  >>> browser.getControl(name='form.body').value = u'test message'
+  >>> browser.getControl('Send').click()
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...Your message has been sent...
+  ...
+
+Registered users with an email address can request a password change:
+
+  >>> root['myuniversity']['users'].addUser('forgetful', 'secret',title='Bob Forgetful',
+  ...                               description='A forgetful user', email='aa@aa.ng')
+  >>> browser.open('http://localhost/myuniversity/changepw')
+  >>> browser.getControl(name="form.identifier").value = 'forgetful'
+  >>> browser.getControl(name="form.email").value = 'aa@aa.ng'
+  >>> browser.getControl("Send login credentials").click()
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...An email with your user name and password has been sent to aa@aa.ng...
+
+Now we login as manager::
+
+  >>> browser.addHeader('Authorization', 'Basic mgr:mgrpw')
+  >>> browser.handleErrors = False
+
+We can then get an edit view of the configuration container::
+
+  >>> browser.open('http://localhost/myuniversity/configuration')
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...<form action="http://localhost/myuniversity/configuration/@@index"
+  ...
+
+The edit form contains the default value for the university name::
+
+  >>> 'Sample University' in browser.contents
+  True
+
+We can perform several actions on the edit form::
+
+  >>> browser.getControl("Save", index=0).click()
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...Form has been saved...
+  ...
+
+  >>> browser.open('http://localhost/myuniversity/configuration')
+  >>> browser.getControl("Update plugins").click()
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...Plugins were updated. See log file for details...
+  ...
+
+The default frontpage HTML has been saved in a dictionary
+and is properly rendered on the frontpage of the portal:
+
+  >>> browser.open('http://localhost/myuniversity')
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...<h1>Welcome to WAeUP.Kofa...</h1>
+  ...
+
+The German part is really not being rendered:
+
+  >>> 'Willkommen' in browser.contents
+  False
+
+
+If we change to German so that the German part of frontpage.rst is rendered:
+
+  >>> browser.open('http://localhost/myuniversity//@@change_language?lang=de')
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...<h1>Willkommen auf WAeUP.Kofa...</h1>
+  ...
+
+The English part is really not being rendered:
+
+  >>> 'Welcome' in browser.contents
+  False
+
+Switch back to English:
+
+  >>> browser.open('http://localhost/myuniversity//@@change_language?lang=en')
+
+We can export a university as XML::
+
+  >>> browser.open('http://localhost/myuniversity/export.xml')
+  >>> print browser.contents
+  <?xml version="1.0" encoding="utf-8" ?>
+  <pickle>
+  ...
+  </pickle>
+
+  >>> print browser.headers
+  Status: 200 Ok
+  Content-Length: ...
+  Content-Type: text/xml; charset=UTF-8
+  X-Powered-By: Zope (www.zope.org), Python (www.python.org)
+
+
+Officers
+========
+
+  >>> browser.open('http://localhost/myuniversity')
+  >>> browser.getLink('Officers').click()
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...Officers...
+  ...
+
+We can add officers:
+
+  >>> browser.getLink("Add officer").click()
+  >>> browser.getControl(name="form.name").value = 'bob'
+  >>> browser.getControl(name="form.title").value = 'Bob The User'
+  >>> browser.getControl(name="password").value = 'secret'
+  >>> browser.getControl(name="control_password").value = 'secret'
+  >>> browser.getControl(name="form.email").value = 'xx@yy.zz'
+  >>> browser.getControl(name="form.phone.country").value = ['+234']
+  >>> browser.getControl(name="form.phone.area").value = '123'
+  >>> browser.getControl(name="form.phone.ext").value = '45678'
+  >>> browser.getControl("Add officer").click()
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...<td>bob</td>
+  ...
+
+We can edit user bob:
+
+  >>> browser.getControl("Manage", index=0).click()
+  >>> browser.getControl("Save", index=0).click()
+  >>> browser.getControl("Cancel", index=0).click()
+
+We can add site roles which are then displayed on the user container page.
+Since the test browser does not use javascript, we have to add site roles
+manually by setting the roles attribute:
+
+  >>> print root['myuniversity']['users']['bob'].roles
+  []
+  >>> root['myuniversity']['users']['bob'].roles = ['waeup.ApplicationsOfficer']
+  >>> print root['myuniversity']['users']['bob'].roles
+  ['waeup.ApplicationsOfficer']
+  >>> browser.open('http://localhost/myuniversity/users')
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...<td nowrap>Applications Officer (view only) <br /></td>
+  ...
+
+Officers can't be added twice:
+
+  >>> browser.open('http://localhost/myuniversity/users/add')
+  >>> browser.getControl(name="form.name").value = 'bob'
+  >>> browser.getControl(name="form.title").value = 'Bob The User'
+  >>> browser.getControl(name="password").value = 'secret'
+  >>> browser.getControl(name="control_password").value = 'secret'
+  >>> browser.getControl(name="form.email").value = 'xx@yy.zz'
+  >>> browser.getControl(name="form.phone.country").value = ['+234']
+  >>> browser.getControl(name="form.phone.area").value = '123'
+  >>> browser.getControl(name="form.phone.ext").value = '45678'
+  >>> browser.getControl("Add officer").click()
+  >>> 'The userid chosen already exists' in browser.contents
+  True
+
+Officers can be deleted:
+
+  >>> browser.open('http://localhost/myuniversity/users')
+  >>> browser.getControl("Remove", index=0).click()
+  >>> 'User account bob successfully deleted' in browser.contents
+  True
+
+
+
+Contact Form
+============
+
+Let's enter the contact form::
+
+  >>> browser.open('http://localhost/myuniversity/contactadmin')
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...Contact
+  ...
+
+We fill the form  (this will send a real message to
+contact@waeup.org)::
+
+  >>> browser.open('http://localhost/myuniversity/contactadmin')
+  >>> browser.getControl(name='form.body').value = "test message"
+  >>> browser.getControl('Send').click()
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...Your message has been sent...
+  ...
+
+If this test fails, chances are, that the local machine has no SMTP
+server installed.
+
+
+
+Faculties
+=========
+
+Faculties are stored in a special container of `IUniversity`
+instances. The container is called ``faculties`` and provides an
+add-form to add new faculties::
+
+  >>> browser.open('http://localhost/myuniversity/faculties/manage')
+  >>> browser.getControl('Add faculty').click()
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...*...
+  ...<span>Name of faculty</span>:
+  ...
+  >>> browser.getControl('Cancel').click()
+  >>> browser.open('http://localhost/myuniversity/faculties/add')
+
+We fill in a new name for our new faculty::
+
+  >>> ctrl = browser.getControl(name='form.title')
+  >>> ctrl.value = 'TestFac'
+
+Furthermore we add a prefix and a code (kind of abbreviation):
+
+  >>> browser.getControl(name='form.code').value = 'TF'
+
+Finally we click on 'Add Faculty' to add the new thing::
+
+  >>> browser.getControl('Add faculty').click()
+
+We can view a faculty by browsing a URL like this::
+
+  >>> browser.open('http://localhost/myuniversity/faculties/TF')
+
+Afterwards, the faculty should be visible:
+
+  >>> browser.open('http://localhost/myuniversity/faculties')
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+  ...<h1 class="kofa-content-label">Academic Section</h1>
+  ...<td> <a href="http://localhost/myuniversity/faculties/TF"> <span>TF</span></a></td>
+  ...
+
+We can 'visit' each faculty by clicking on the appropriate link:
+
+  >>> browser.getLink('TF').click()
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...Faculty of TestFac (TF)...
+  ...
+
+If we add the same faculty twice, an error message will occur:
+
+  >>> browser.open('http://localhost/myuniversity/faculties/add')
+  >>> ctrl = browser.getControl(name='form.title')
+  >>> ctrl.value = 'TestFac'
+  >>> browser.getControl(name='form.code').value = 'TF'
+  >>> browser.getControl('Add faculty').click()
+  >>> 'The faculty code chosen already exists.' in browser.contents
+  True
+
+Modifying faculties
+-------------------
+
+A faculty can directly be reached by its code:
+
+  >>> browser.open('http://localhost/myuniversity/faculties/TF')
+
+We can change the settings for a faculty by clicking on the
+provided 'Manage  faculty' button:
+
+  >>> browser.getLink('Manage faculty').click()
+
+Let's set a new title and save the form:
+
+  >>> browser.getControl(name='form.title').value = "My renamed faculty"
+  >>> browser.getControl(name='form.actions.save').click()
+
+Our faculty was indeed renamed to ``My renamed faculty``:
+
+  >>> browser.open('http://localhost/myuniversity/faculties')
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...<h1 class="kofa-content-label">Academic Section</h1>
+  ...<td> <a href="http://localhost/myuniversity/faculties/TF"> <span>TF</span></a></td>
+  ...<td>Faculty of My renamed faculty (TF)</td>
+  ...
+
+We can grant local roles:
+
+  >>> root['myuniversity']['users'].addUser('bob', 'secret',title='Bob',
+  ...                               description='A sample user')
+  >>> browser.open('http://localhost/myuniversity/faculties/TF/manage')
+  >>> browser.getControl(name="user").value = ['bob']
+  >>> browser.getControl(
+  ...     name="local_role").value = ['waeup.local.DepartmentManager']
+  >>> browser.getControl("Add local role").click()
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...<td>bob</td>
+  ...
+
+On the officers page the new local role is displayed:
+
+  >>> browser.getLink("Officers").click()
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...<td>Department Manager:...
+  ...
+
+The local role can be removed again:
+
+  >>> browser.open('http://localhost/myuniversity/faculties/TF/manage')
+  >>> ctrl = browser.getControl(name='role_id')
+  >>> browser.getControl("Remove selected local roles").click()
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...No local role selected...
+  ...
+
+  >>> browser.open('http://localhost/myuniversity/faculties/TF/manage')
+  >>> ctrl = browser.getControl(name='role_id')
+  >>> ctrl.getControl(value='bob|waeup.local.DepartmentManager').selected = True
+  >>> browser.getControl("Remove selected local roles").click()
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...Local role successfully removed...
+  ...
+
+Deleting faculties
+------------------
+
+  >>> browser.open('http://localhost/myuniversity/faculties/manage')
+  >>> browser.getControl('Cancel').click()
+  >>> print browser.url
+  http://localhost/myuniversity/faculties
+  >>> browser.getLink('Manage academic section').click()
+  >>> browser.getControl('Add faculty').click()
+  >>> ctrl = browser.getControl(name='form.title')
+  >>> ctrl.value = 'Second Faculty'
+  >>> browser.getControl(name='form.code').value = 'TF2'
+  >>> browser.getControl('Add faculty').click()
+  >>> browser.open('http://localhost/myuniversity/faculties/manage')
+  >>> browser.getControl("Remove selected", index=0).click()
+  >>> 'No item selected' in browser.contents
+  True
+  >>> browser.open('http://localhost/myuniversity/faculties/manage')
+  >>> ctrl = browser.getControl(name='val_id')
+  >>> ctrl.getControl(value='TF2').selected = True
+  >>> browser.getControl("Remove selected", index=0).click()
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...Successfully removed:...
+  ...
+
+Departments
+===========
+
+Adding departments
+------------------
+
+Departments are stored in :class:`IFaculty` instances with their code
+as key. Faculties therefore are also department containers. Faculties
+provides an add-form to add new departments:
+
+  >>> browser.open('http://localhost/myuniversity/faculties/TF/add')
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...*</span>
+  ...<span>Name of department</span>:
+  ...
+  >>> browser.getControl('Cancel').click()
+  >>> browser.open('http://localhost/myuniversity/faculties/TF/add')
+
+We fill in a new name for our new department:
+
+  >>> ctrl = browser.getControl(name='form.title')
+  >>> ctrl.value = 'TestDept'
+
+Furthermore we add a code (kind of abbreviation):
+
+  >>> browser.getControl(name='form.code').value = 'TD'
+
+Finally we click on 'Add Department' to add the new thing::
+
+  >>> browser.getControl('Add department').click()
+
+If we try to register a department under the same code twice we will
+get an error:
+
+  >>> browser.open('http://localhost/myuniversity/faculties/TF/add')
+  >>> ctrl = browser.getControl(name='form.title')
+  >>> ctrl.value = 'Another TestDept with same code'
+  >>> browser.getControl(name='form.code').value = 'TD'
+  >>> browser.getControl('Add department').click()
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...The code chosen already exists in this faculty...
+  ...
+
+We can view a department by browsing a URL like this::
+
+  >>> browser.open('http://localhost/myuniversity/faculties/TF/TD')
+
+Afterwards, the department should be visible::
+
+  >>> browser.open('http://localhost/myuniversity/faculties/TF')
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...<h1 class="kofa-content-label">Departments</h1>
+  ...<td> <a href="http://localhost/myuniversity/faculties/TF/TD"> <span>TD</span></a></td>
+  ...<td>Department of TestDept (TD)</td>
+  ...
+
+
+Modifying departments
+---------------------
+
+We can change the settings for a department by clicking on the
+provided 'Edit department' button:
+
+  >>> browser.open('http://localhost/myuniversity/faculties/TF/TD')
+  >>> browser.getLink('Manage department').click()
+
+Let's set a new title and save the form:
+
+  >>> browser.getControl(name='form.title').value = "My test dept"
+  >>> browser.getControl(name='form.actions.save').click()
+
+Clicking 'Save' we will stay on the settings form. So we can change
+the department again.
+
+  >>> browser.getControl(name='form.title').value = "My renamed dept"
+  >>> ctrl = browser.getControl("Save")
+  >>> ctrl.click()
+
+
+Our department was indeed renamed to ``My renamed dept``:
+
+  >>> browser.open('http://localhost/myuniversity/faculties/TF')
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+  ...<h1 class="kofa-content-label">Departments</h1>
+  ...<td> <a href="http://localhost/myuniversity/faculties/TF/TD"> <span>TD</span></a></td>
+  ...<td>Department of My renamed dept (TD)</td>
+  ...
+
+We can grant local roles:
+
+  >>> browser.open('http://localhost/myuniversity/faculties/TF/TD/manage')
+  >>> browser.getControl(name="user").value = ['bob']
+  >>> browser.getControl(
+  ...     name="local_role").value = ['waeup.local.DepartmentManager']
+  >>> browser.getControl("Add local role").click()
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...<td>bob</td>
+  ...
+
+The local role can be removed again:
+
+  >>> browser.open('http://localhost/myuniversity/faculties/TF/TD/manage')
+  >>> ctrl = browser.getControl(name='role_id')
+  >>> browser.getControl("Remove selected local roles").click()
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...No local role selected...
+  ...
+
+  >>> browser.open('http://localhost/myuniversity/faculties/TF/TD/manage')
+  >>> ctrl = browser.getControl(name='role_id')
+  >>> ctrl.getControl(
+  ...     value='bob|waeup.local.DepartmentManager').selected = True
+  >>> browser.getControl("Remove selected local roles").click()
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...Local role successfully removed...
+  ...
+
+Deleting departments
+--------------------
+
+  >>> browser.open('http://localhost/myuniversity/faculties/TF/manage')
+  >>> browser.getControl('Cancel', index=0).click()
+  >>> print browser.url
+  http://localhost/myuniversity/faculties/TF
+  >>> browser.getLink('Manage faculty').click()
+  >>> browser.getControl('Add department').click()
+  >>> ctrl = browser.getControl(name='form.title')
+  >>> ctrl.value = 'Second Department'
+  >>> browser.getControl(name='form.code').value = 'TD2'
+  >>> browser.getControl('Add department').click()
+  >>> browser.open('http://localhost/myuniversity/faculties/TF/manage')
+  >>> browser.getControl("Remove selected", index=0).click()
+  >>> 'No item selected' in browser.contents
+  True
+  >>> browser.open('http://localhost/myuniversity/faculties/TF/manage')
+  >>> ctrl = browser.getControl(name='val_id')
+  >>> ctrl.getControl(value='TD2').selected = True
+  >>> browser.getControl("Remove selected", index=0).click()
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...Successfully removed:...
+  ...
+
+
+Courses
+=======
+
+Once we have a department, we can add courses.
+
+Adding courses
+--------------
+
+Courses are stored in :class:`ICoursesContainer` instances with their
+code as key. CoursesContainers are normally availabe as `course`
+attribute of :class:`waeup.kofa.university.department.Department`
+instances.
+
+To ease the life of officers we do not require to browse the
+coursescontainers (which have a rather flat user interface), but
+provide adding of courses in department views.
+
+Each department provides a ``Add course`` action button near top.
+
+Departments provide an add-form to add new courses:
+
+  >>> dept_url = 'http://localhost/myuniversity/faculties/TF/TD'
+  >>> browser.open(dept_url + '/manage')
+  >>> browser.getControl('Add course').click()
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...*</span>
+  ...<span>Title of course</span>:
+  ...
+
+We fill in a name for our new course:
+
+  >>> ctrl = browser.getControl(name='form.title')
+  >>> ctrl.value = 'My Course 1'
+
+Furthermore we add a code (kind of abbreviation):
+
+  >>> browser.getControl(name='form.code').value = 'COURSE1'
+
+This course will take place in the the first semester, so we set the
+`semester` value to 1:
+
+  >>> ctrl = browser.getControl(name='form.semester')
+  >>> ctrl.options
+  ['1', '2', '3', '9']
+
+  >>> ctrl.displayOptions
+  ['1st Semester', '2nd Semester', 'Combined', 'N/A']
+
+  >>> ctrl.value = ['1']
+
+Finally, we create the course:
+
+  >>> browser.getControl('Add course').click()
+
+If we try to register a course under the same code twice we will
+get an error:
+
+  >>> browser.open(dept_url + '/addcourse')
+  >>> ctrl = browser.getControl(name='form.title')
+  >>> ctrl.value = 'Another course with same code'
+  >>> browser.getControl(name='form.code').value = 'COURSE1'
+  >>> browser.getControl('Add course').click()
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...A course with same code already exists:...
+  ...
+
+Our course will be linked under the code on the department page:
+
+  >>> browser.open(dept_url)
+  >>> browser.getLink('COURSE1').click()
+  >>> browser.url
+  'http://localhost/myuniversity/faculties/TF/TD/courses/COURSE1'
+
+Before we really add a course we can cancel the action and will be
+redirected to the department page:
+
+  >>> browser.open(dept_url + '/addcourse')
+  >>> browser.getControl('Cancel').click()
+  >>> browser.url
+  'http://localhost/myuniversity/faculties/TF/TD'
+
+
+Modifying courses
+-----------------
+
+We can change the settings for a course by clicking on the provided
+'Edit settings' link:
+
+  >>> browser.open(dept_url + '/courses/COURSE1')
+  >>> browser.getLink('Edit course').click()
+
+When modifying a course, we cannot change the code any more:
+
+  >>> browser.getControl(name='form.code')
+  Traceback (most recent call last):
+  ...
+  LookupError: name 'form.code'
+
+Let's set a new title and save the form:
+
+  >>> browser.getControl(name='form.title').value = "My test course"
+  >>> browser.getControl(name='form.actions.save').click()
+
+Clicking 'Save' we will stay on the settings form. So we can change
+the course again. If we click ``Cancel`` nothing will be
+changed:
+
+  >>> browser.getControl(name='form.title').value = "Blah"
+  >>> browser.getControl('Cancel').click()
+
+Our course was not renamed to ``Blah``:
+
+  >>> browser.open('http://localhost/myuniversity/faculties/TF/TD')
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...<td>My test course</td>...
+  ...
+
+Searching courses
+-----------------
+
+  >>> browser.open('http://localhost/myuniversity/faculties/search')
+  >>> browser.getControl(name='query').value = "My test course"
+  >>> browser.getControl('Search').click()
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...faculties/TF/TD/courses/COURSE1">COURSE1</a>
+  ...
+
+  >>> browser.open('http://localhost/myuniversity/faculties/search')
+  >>> browser.getControl(name='query').value = "COURSE1"
+  >>> browser.getControl('Search').click()
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...faculties/TF/TD/courses/COURSE1">COURSE1</a>
+  ...
+
+  >>> browser.getControl(name='query').value = "Nonsense"
+  >>> browser.getControl('Search').click()
+  >>> 'Search Results' in browser.contents
+  False
+  >>> '<div class="alert alert-warning">No object found.</div>' in browser.contents
+  True
+
+  >>> browser.getControl(name='query').value = ""
+  >>> browser.getControl('Search').click()
+  >>> 'Empty search string' in browser.contents
+  True
+
+We can grant local roles:
+
+  >>> browser.open('http://localhost/myuniversity/faculties/TF/TD/courses/COURSE1/manage')
+  >>> browser.getControl(name="user").value = ['bob']
+  >>> browser.getControl(
+  ...     name="local_role").value = ['waeup.local.Lecturer']
+  >>> browser.getControl("Add local role").click()
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...<td>bob</td>
+  ...
+
+On the portal user page the new local role is displayed:
+
+  >>> browser.getLink("Officers").click()
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...<td>Lecturer:...
+  ...
+
+The local role can be removed again:
+
+  >>> browser.open('http://localhost/myuniversity/faculties/TF/TD/courses/COURSE1/manage')
+  >>> ctrl = browser.getControl(name='role_id')
+  >>> browser.getControl("Remove selected local roles").click()
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...No local role selected...
+  ...
+
+  >>> browser.open('http://localhost/myuniversity/faculties/TF/TD/courses/COURSE1/manage')
+  >>> ctrl = browser.getControl(name='role_id')
+  >>> ctrl.getControl(value='bob|waeup.local.Lecturer').selected = True
+  >>> browser.getControl("Remove selected local roles").click()
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...Local role successfully removed...
+  ...
+
+Deleting courses
+----------------
+
+We can delete courses by browsing the manage page of the containing
+department and checking the appropriate select box and clicking the
+´´Remove selected´´ button.
+
+  >>> browser.open(
+  ...     'http://localhost/myuniversity/faculties/TF/TD/@@manage#tab-2')
+  >>> 'My test course' in browser.contents
+  True
+
+  >>> browser.getControl('Cancel', index=0).click()
+  >>> browser.getLink('Manage department').click()
+  >>> browser.getControl('Remove selected courses').click()
+  >>> 'No item selected' in browser.contents
+  True
+
+  >>> browser.getControl(
+  ...    name='val_id').getControl(value='COURSE1').selected = True
+  >>> browser.getControl('Remove selected courses').click()
+
+  >>> browser.open('http://localhost/myuniversity/faculties/TF/TD')
+  >>> 'My renamed course' in browser.contents
+  False
+
+
+Deleting departments with courses
+---------------------------------
+
+  >>> browser.open('http://localhost/myuniversity/faculties/TF/manage')
+  >>> browser.getControl('Cancel', index=0).click()
+  >>> browser.getLink('Manage faculty').click()
+  >>> browser.getControl('Add department').click()
+  >>> ctrl = browser.getControl(name='form.title')
+  >>> ctrl.value = 'Third Department'
+  >>> browser.getControl(name='form.code').value = 'TD3'
+  >>> browser.getControl('Add department').click()
+  >>> browser.open('http://localhost/myuniversity/faculties/TF/TD3/manage')
+  >>> browser.getControl('Add course').click()
+  >>> ctrl = browser.getControl(name='form.title')
+  >>> ctrl.value = 'My Course 5'
+  >>> browser.getControl(name='form.code').value = 'COURSE5'
+  >>> browser.getControl('Add course').click()
+  >>> browser.open('http://localhost/myuniversity/faculties/TF/manage')
+  >>> ctrl = browser.getControl(name='val_id')
+  >>> ctrl.getControl(value='TD3').selected = True
+  >>> browser.getControl("Remove selected", index=0).click()
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...Successfully removed:...
+  ...
+
+Certificates
+============
+
+Once we have a department, we can add also certificats.
+
+Adding certificates
+-------------------
+
+Certificates are stored in :class:`ICertificatesContainer` instances
+with their code as key. CertificatesContainers are normally availabe as
+`certificates` attribute of
+:class:`waeup.kofa.university.department.Department` instances.
+
+To ease the life of officers we do not require to browse the
+certificatescontainers (which have in fact no user interface), but
+provide adding of certificates in department views.
+
+Each department provides a ``Add certificate`` action button near top.
+
+Departments provide an add-form to add new certificates:
+
+  >>> dept_url = 'http://localhost/myuniversity/faculties/TF/TD'
+  >>> browser.open(dept_url + '/manage')
+  >>> browser.getControl('Add certificate').click()
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...*</span>
+  ...<span>Title</span>:
+  ...
+
+We fill in a name for our new cert:
+
+  >>> ctrl = browser.getControl(name='form.title')
+  >>> ctrl.value = 'My Certificate 1'
+
+Furthermore we add a code (kind of abbreviation):
+
+  >>> browser.getControl(name='form.code').value = 'CERT1'
+
+Set the remaining required fields:
+
+  >>> browser.getControl(name='form.study_mode').value = ['ug_ft']
+  >>> browser.getControl(name='form.start_level').value = ['100']
+  >>> browser.getControl(name='form.end_level').value = ['400']
+  >>> browser.getControl(name='form.application_category').value = ['basic']
+
+Finally, we create the certificate:
+
+  >>> browser.getControl('Add certificate').click()
+
+If we try to register a certificate under the same code twice we will
+get an error:
+
+  >>> browser.open(dept_url + '/addcertificate')
+  >>> ctrl = browser.getControl(name='form.title')
+  >>> ctrl.value = 'Another cert with same code'
+  >>> browser.getControl(name='form.code').value = 'CERT1'
+  >>> browser.getControl(name='form.study_mode').value = ['ug_ft']
+  >>> browser.getControl(name='form.start_level').value = ['100']
+  >>> browser.getControl(name='form.end_level').value = ['400']
+  >>> browser.getControl(name='form.application_category').value = ['basic']
+
+  >>> browser.getControl('Add certificate').click()
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...A certificate with same code already exists:...
+  ...
+
+Our certificate will be linked under the code on the department page:
+
+  >>> browser.open(dept_url)
+  >>> browser.getLink('CERT1').click()
+  >>> browser.url
+  'http://localhost/myuniversity/faculties/TF/TD/certificates/CERT1'
+
+Before we really add a certificate we can cancel the action and will be
+redirected to the department page:
+
+  >>> browser.open(dept_url + '/addcertificate')
+  >>> browser.getControl('Cancel').click()
+  >>> browser.url
+  'http://localhost/myuniversity/faculties/TF/TD'
+
+
+Modifying certificates
+----------------------
+
+We can change the settings for a cert by clicking on the provided
+'Edit certificate' link:
+
+  >>> browser.open(dept_url + '/certificates/CERT1')
+  >>> browser.getLink('Manage certificate').click()
+
+When modifying a certificate, we cannot change the code any more:
+
+  >>> browser.getControl(name='form.code')
+  Traceback (most recent call last):
+  ...
+  LookupError: name 'form.code'
+
+Let's set a new title and save the form:
+
+  >>> browser.getControl(name='form.title').value = "My test cert"
+  >>> browser.getControl(name='form.actions.save').click()
+
+Clicking 'Save' we will stay on the settings form. So we can change
+the cert again.
+
+  >>> browser.getControl(name='form.title').value = "My renamed cert"
+  >>> ctrl = browser.getControl("Save",index=0)
+  >>> ctrl.click()
+
+If we go to the settings page and click ``Cancel`` nothing will be
+changed:
+
+  >>> browser.getControl(name='form.title').value = "Blah"
+  >>> browser.getControl('Cancel',index=0).click()
+
+Our certificate was indeed renamed to ``My renamed cert`` and not to
+``Blah``:
+
+  >>> browser.open('http://localhost/myuniversity/faculties/TF/TD')
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...<td>My renamed cert</td>...
+  ...
+
+Searching certificates
+----------------------
+
+  >>> browser.open('http://localhost/myuniversity/faculties/search')
+  >>> browser.getControl(name='query').value = "My renamed cert"
+  >>> browser.getControl('Search').click()
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...faculties/TF/TD/certificates/CERT1">CERT1</a>
+  ...
+
+  >>> browser.open('http://localhost/myuniversity/faculties/search')
+  >>> browser.getControl(name='query').value = "CERT1"
+  >>> browser.getControl('Search').click()
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...faculties/TF/TD/certificates/CERT1">CERT1</a>
+  ...
+
+Deleting certificates
+---------------------
+
+We can delete certificates by browsing the manage page of the
+containing department and checking the appropriate select box and
+clicking the ´´Remove selected´´ button.
+
+  >>> browser.open(
+  ...    'http://localhost/myuniversity/faculties/TF/TD/@@manage#tab-3')
+  >>> 'My renamed cert' in browser.contents
+  True
+
+  >>> browser.getControl('Remove selected certificates').click()
+  >>> 'No item selected' in browser.contents
+  True
+
+  >>> browser.getControl(name='val_id').getControl(
+  ...    value='CERT1').selected = True
+  >>> browser.getControl('Remove selected certificates').click()
+  >>> 'My renamed cert' in browser.contents
+  False
+
+
+Certificate Courses
+===================
+
+Once we have a certificate, we can add also certificate courses. These
+are referrers of courses with some extra data.
+
+Before we can work with certificate courses, we need some certificates
+and courses to be available.
+
+  >>> browser.open(dept_url + '/addcourse')
+  >>> ctrl = browser.getControl(name='form.title')
+  >>> ctrl.value = 'Another course with same code'
+  >>> browser.getControl(name='form.code').value = 'COURSE1'
+  >>> browser.getControl(name='form.title').value = 'Course 1'
+  >>> browser.getControl('Add course').click()
+
+  >>> browser.open(dept_url + '/addcourse')
+  >>> ctrl = browser.getControl(name='form.title')
+  >>> ctrl.value = 'Another course with same code'
+  >>> browser.getControl(name='form.code').value = 'COURSE2'
+  >>> browser.getControl(name='form.title').value = 'Course 2'
+  >>> browser.getControl('Add course').click()
+
+  >>> browser.open(dept_url + '/addcertificate')
+  >>> ctrl = browser.getControl(name='form.title')
+  >>> ctrl.value = 'Another cert with same code'
+  >>> browser.getControl(name='form.code').value = 'CERT1'
+  >>> browser.getControl(name='form.title').value = 'Certificate 1'
+  >>> browser.getControl(name='form.study_mode').value = ['ug_ft']
+  >>> browser.getControl(name='form.start_level').value = ['100']
+  >>> browser.getControl(name='form.end_level').value = ['400']
+  >>> browser.getControl(name='form.application_category').value = ['basic']
+  >>> browser.getControl('Add certificate').click()
+
+  >>> browser.open(dept_url + '/addcertificate')
+  >>> ctrl = browser.getControl(name='form.title')
+  >>> ctrl.value = 'Another cert with same code'
+  >>> browser.getControl(name='form.code').value = 'CERT2'
+  >>> browser.getControl(name='form.title').value = 'Certificate 2'
+  >>> browser.getControl(name='form.study_mode').value = ['ug_ft']
+  >>> browser.getControl(name='form.start_level').value = ['100']
+  >>> browser.getControl(name='form.end_level').value = ['400']
+  >>> browser.getControl(name='form.application_category').value = ['basic']
+  >>> browser.getControl('Add certificate').click()
+
+
+Adding certificate courses
+--------------------------
+
+Certcourses are stored in :class:`ICertificate` instances
+with their code as key.
+
+Each certificate provides a ``Add certificate course`` action button near top.
+
+Certificates provide an add-form to add new certcourses:
+
+  >>> cert_url = dept_url + '/certificates/CERT1'
+  >>> browser.open(cert_url + '/manage')
+  >>> browser.getControl('Add certificate course').click()
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...*</span>
+  ...<span>Level</span>:
+  ...
+
+In the add-form we will get a list of available courses to select
+from. This list will contain all courses stored in the site, not only
+the ones from local department:
+
+  >>> ctrl = browser.getControl(name='form.course')
+  >>> ctrl.displayOptions
+  ['--', 'COURSE1 - Course 1', 'COURSE2 - Course 2']
+
+We select the first course and create our certificate course:
+
+  >>> ctrl.getControl('COURSE1').selected = True
+  >>> browser.getControl(name='form.level').value = ['100']
+  >>> browser.getControl('Add certificate course').click()
+
+Our certificate course will be linked on the parent certificate page:
+
+  >>> browser.open(cert_url)
+  >>> browser.getLink('COURSE1_100').click()
+  >>> browser.url
+  'http://localhost/my...sity/faculties/TF/TD/certificates/CERT1/COURSE1_100'
+
+We can't add the same certificate course twice:
+
+  >>> cert_url = dept_url + '/certificates/CERT1'
+  >>> browser.open(cert_url + '/manage')
+  >>> browser.getControl('Add certificate course').click()
+  >>> ctrl = browser.getControl(name='form.course')
+  >>> ctrl.getControl('COURSE1').selected = True
+  >>> browser.getControl(name='form.level').value = ['100']
+  >>> browser.getControl('Add certificate course').click()
+  >>> 'The chosen certificate course is already' in browser.contents
+  True
+
+When we started to add a new certificate course, we can also cancel the
+process before submitting. This will bring us back to the certificate
+page:
+
+  >>> browser.open(cert_url + '/addcertificatecourse')
+  >>> browser.getControl('Cancel').click()
+  >>> browser.url
+  'http://localhost/myuniversity/faculties/TF/TD/certificates/CERT1'
+
+
+Modifying certificate courses
+-----------------------------
+
+We can change the settings for a certcourse by clicking on the
+provided 'Edit certificate course' link:
+
+  >>> browser.open(cert_url + '/COURSE1_100')
+  >>> browser.getLink('Edit certificate course').click()
+
+If we just click 'Save and return' nothing will change:
+
+  >>> browser.getControl("Save and return").click()
+  >>> browser.getLink('COURSE1_100').click()
+  >>> browser.url
+  'http://localhost/myun.../TF/TD/certificates/CERT1/COURSE1_100'
+
+Let's set a new level (it was 100 before) and save the form. This will
+bring us to the certificate index page afterwards:
+
+  >>> browser.open(cert_url + '/COURSE1_100/manage')
+  >>> browser.getControl(name='form.level').value = ['200']
+  >>> browser.getControl("Save and return").click()
+
+As we changed the level, also the URL will change:
+
+  >>> browser.getLink('COURSE1_200').click()
+  >>> browser.url
+  'http://localhost/myun.../TF/TD/certificates/CERT1/COURSE1_200'
+
+If we go to the settings page and click ``Cancel`` nothing will be
+changed:
+
+  >>> browser.getLink('Edit certificate course').click()
+  >>> browser.getControl(name='form.level').value = ['400']
+  >>> browser.getControl('Cancel').click()
+
+Our certcourse provides a new level of 200 and not 400:
+
+  >>> browser.open(cert_url + '/COURSE1_200')
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+  ...<td>Level:</td>
+  ...<td>200 (Year 2)</td>
+  ...
+
+Searching certificate courses
+-----------------------------
+
+  >>> browser.open('http://localhost/myuniversity/faculties/search')
+  >>> browser.getControl(name='query').value = "COURSE1"
+  >>> browser.getControl('Search').click()
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"...
+  ...faculties/TF/TD/certificates/CERT1/COURSE1_200">COURSE1</a>
+  ...
+
+  >>> browser.getControl(name='query').value = "*"
+  >>> browser.getControl('Search').click()
+  >>> 'Search string not allowed' in browser.contents
+  True
+
+Deleting certificate courses
+----------------------------
+
+We can delete certcourses by browsing the containing certificate manage page:
+
+  >>> browser.open(cert_url + '/manage#tab-2')
+  >>> 'COURSE1_200</a>' in browser.contents
+  True
+  >>> browser.getControl('Remove selected certificate courses').click()
+  >>> 'No item selected' in browser.contents
+  True
+  >>> browser.getControl(name='val_id').getControl(
+  ...    value='COURSE1_200').selected = True
+  >>> browser.getControl('Remove selected certificate courses').click()
+  >>> 'Successfully removed: COURSE1_200' in browser.contents
+  True
+
+  >>> 'COURSE1_200</a>' in browser.contents
+  False
+
+
+
+Data Center
+===========
+
+The data center helps us uploading files for later import or similar.
+
+  >>> browser.open('http://localhost/myuniversity')
+  >>> browser.getLink('Data Center').click()
+
+Setting the file path
+---------------------
+
+A datacenter stores files in a path in filesystem. By default this is
+a directory in the sources:
+
+  >>> print browser.contents
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+  ...
+  <b>Storage path:</b> <span>.../parts/test/datacenter</span>
+  ...
+
+Going to 'Settings` we can change the path:
+
+  >>> browser.getLink('Edit settings').click()
+  >>> pathsetting = browser.getControl(name='newpath')
+
+We create a directory and set it as our upload path:
+
+  >>> import os
+  >>> cwd = os.getcwd()
+  >>> uploadpath = os.path.join(cwd, 'testfiles')
+  >>> os.mkdir(uploadpath)
+  >>> pathsetting.value = uploadpath
+
+And submit the form:
+
+  >>> browser.getControl(name='save').click()
+
+We clean up the set directory path, as there might be some files be
+copied some files from installation:
+
+  >>> files = os.listdir(uploadpath)
+  >>> for filename in files:
+  ...   if os.path.isdir(os.path.join(uploadpath, filename)):
+  ...     continue
+  ...   os.unlink(os.path.join(uploadpath, filename))
+
+We also remove any existing 'accesscodes' subdir:
+
+  >>> import shutil
+  >>> for filename in files:
+  ...   if not os.path.isdir(os.path.join(uploadpath, filename)):
+  ...     continue
+  ...   if filename != 'accesscodes':
+  ...     continue
+  ...   shutil.rmtree(os.path.join(uploadpath, filename))
+
+The new upload directory is now empty, except from the logs and other
+standard dirs, which are created automatically:
+
+  >>> sorted(os.listdir(uploadpath))
+  ['deleted', 'finished', 'logs', 'unfinished']
+
+
+Uploading files
+---------------
+
+Now we can upload files. Most interesting files might be CSV files,
+that can be imported lateron. We create a CSV file containing faculty
+descriptions:
+
+  >>> open('faculties.csv', 'wb').write(
+  ... """code,title,title_prefix
+  ... FA,Arts,faculty
+  ... FS,Sciences,faculty
+  ... """)
+
+Now we can upload this file. To do this, we first go to the upload
+page:
+
+  >>> browser.getLink('Upload data').click()
+
+and enter the appropriate data in the form:
+
+  >>> filewidget = browser.getControl(name='uploadfile:file')
+  >>> filewidget
+  <Control name='uploadfile:file' type='file'>
+
+A sidenote for developers: by marking the filewidget with the
+``:file`` extension, we tell Zope to handle this field as a file
+widget.
+
+  >>> import cStringIO
+  >>> filecontents = cStringIO.StringIO(
+  ...   open('faculties.csv', 'rb').read())
+  >>> filewidget.add_file(filecontents, 'text/plain', 'myfaculties.csv')
+
+  >>> browser.getControl(name='SUBMIT').click()
+
+The file was indeed uploaded, with the current userid inserted:
+
+  >>> sorted(os.listdir(uploadpath))
+  ['deleted', 'finished', 'logs', 'myfaculties_zope.mgr.csv', 'unfinished']
+
+We create and upload also a CSV file containing departments:
+
+  >>> open('departments.csv', 'wb').write(
+  ... """code,title,title_prefix,faculty_code
+  ... LIT,Literature,department,FA
+  ... SOC,Sociology,department,FA
+  ... PHY,Physics,department,FS
+  ... INF,Informatics,department,FS
+  ... MAT,Math,department,FS
+  ... """)
+
+  >>> browser.open('http://localhost/myuniversity/datacenter/upload')
+  >>> browser.getControl(name='uploadfile:file').add_file(
+  ...   cStringIO.StringIO(open('departments.csv', 'rb').read()),
+  ...   'text/plain', 'mydepartments.csv')
+  >>> browser.getControl(name='SUBMIT').click()
+
+We create and upload also a CSV file containing courses:
+
+  >>> open('courses.csv', 'wb').write(
+  ... """code,level,title,passmark,credits,semester,faculty,department
+  ... LI1,,Introduction to Literature I,40,2,1,FA,LIT
+  ... LI2,,Introduction to Literature II,40,2,2,FA,LIT
+  ... AN1,000,Analysis I,40,2,1,FS,MAT
+  ... AN2,000,Analysis II,40,2,2,FS,MAT
+  ... """)
+
+  >>> browser.open('http://localhost/myuniversity/datacenter/upload')
+  >>> browser.getControl(name='uploadfile:file').add_file(
+  ...   cStringIO.StringIO(open('courses.csv', 'rb').read()),
+  ...   'text/plain', 'mycourses.csv')
+  >>> browser.getControl(name='SUBMIT').click()
+
+We create and upload also a CSV file containing certificates:
+
+  >>> open('certificates.csv', 'wb').write(
+  ... """code,title,faculty_code,department_code,study_mode,end_level,m_prefix,start_level,application_category
+  ... LBA,BACHELOR OF LITERATURE,FA,LIT,UG,ug_ft,500,LIT,100,basic
+  ... LMA,MASTER OF LITERATURE,FA,LIT,UG,ug_pt,500,LIT,100,cest
+  ... DME,DIPLOMA OF MATH,FS,MAT,DP,dp_ft,200,DME,100,cest
+  ... """)
+
+  >>> browser.open('http://localhost/myuniversity/datacenter/upload')
+  >>> browser.getControl(name='uploadfile:file').add_file(
+  ...   cStringIO.StringIO(open('certificates.csv', 'rb').read()),
+  ...   'text/plain', 'mycertificates.csv')
+  >>> browser.getControl(name='SUBMIT').click()
+
+We create and upload also a CSV file containing certificate courses:
+
+  >>> open('certcourses.csv', 'wb').write(
+  ... """code,faculty_code,department_code,certificate_code,level,mandatory
+  ... LI1,FA,LIT,LBA,100,True
+  ... LI2,FA,LIT,LBA,200,True
+  ... """)
+
+  >>> browser.open('http://localhost/myuniversity/datacenter/upload')
+  >>> browser.getControl(name='uploadfile:file').add_file(
+  ...   cStringIO.StringIO(open('certcourses.csv', 'rb').read()),
+  ...   'text/plain', 'mycertcourses.csv')
+  >>> browser.getControl(name='SUBMIT').click()
+
+
+Importing a CSV file
+--------------------
+
+The import of CSV files is described in batchprocessing.txt.
+
+
+Clean up:
+
+  >>> import os
+  >>> import shutil
+  >>> shutil.rmtree(uploadpath)
+
