Index: /main/waeup.uniben/trunk/buildout-windows.cfg
===================================================================
--- /main/waeup.uniben/trunk/buildout-windows.cfg	(revision 8019)
+++ /main/waeup.uniben/trunk/buildout-windows.cfg	(revision 8020)
@@ -52,5 +52,5 @@
 # This creates only the paster script in bin/.
 recipe = zc.recipe.egg
-eggs = waeup.custom
+eggs = waeup.uniben
        z3c.evalexception>=2.0
        Paste
Index: /main/waeup.uniben/trunk/buildout.cfg
===================================================================
--- /main/waeup.uniben/trunk/buildout.cfg	(revision 8019)
+++ /main/waeup.uniben/trunk/buildout.cfg	(revision 8020)
@@ -62,5 +62,5 @@
 # This creates all scripts in bin/. The kofactl created here is 'faulty'.
 recipe = zc.recipe.egg
-eggs = waeup.custom [beaker]
+eggs = waeup.uniben [beaker]
        z3c.evalexception>=2.0
        Paste
@@ -74,5 +74,5 @@
 # fixed path to zdaemon.conf as argument.
 recipe = zc.recipe.egg
-eggs = waeup.custom [beaker]
+eggs = waeup.uniben [beaker]
 arguments = "${buildout:parts-directory}/etc/zdaemon.conf"
 scripts = kofactl=kofactl
@@ -89,5 +89,5 @@
 [test]
 recipe = zc.recipe.testrunner
-eggs = waeup.custom [beaker, test]
+eggs = waeup.uniben [beaker, test]
 defaults = ['--tests-pattern', '^f?tests$', '-v']
 
@@ -95,8 +95,8 @@
 [i18n]
 recipe = z3c.recipe.i18n:i18n
-packages = waeup.custom
-eggs = waeup.custom
-domain = waeup.custom
-output = src/waeup/custom/locales
+packages = waeup.uniben
+eggs = waeup.uniben
+domain = waeup.uniben
+output = src/waeup/uniben/locales
 zcml =
 
@@ -105,5 +105,5 @@
 [zpasswd]
 recipe = z3c.recipe.dev:script
-eggs = waeup.custom
+eggs = waeup.uniben
 module = zope.app.server.zpasswd
 method = main
@@ -157,5 +157,5 @@
 [waeupdocs]
 recipe = collective.recipe.sphinxbuilder
-eggs = waeup.custom[docs]
+eggs = waeup.uniben[docs]
 source = ${buildout:directory}/docs/source
 build = ${buildout:directory}/docs/build
@@ -164,5 +164,5 @@
 [coverage-detect]
 recipe = zc.recipe.testrunner
-eggs = waeup.custom[beaker]
+eggs = waeup.uniben[beaker]
 defaults = ['--tests-pattern', '^f?tests$', '-v', '--coverage', 'coverage']
 
Index: /main/waeup.uniben/trunk/etc/debug.ini.in
===================================================================
--- /main/waeup.uniben/trunk/etc/debug.ini.in	(revision 8019)
+++ /main/waeup.uniben/trunk/etc/debug.ini.in	(revision 8020)
@@ -54,5 +54,5 @@
 
 [app:zope]
-use = egg:waeup.custom#debug
+use = egg:waeup.uniben#debug
 filter-with = translogger
 exempt-exceptions = zope.security.interfaces.IUnauthorized
Index: /main/waeup.uniben/trunk/etc/deploy.ini.in
===================================================================
--- /main/waeup.uniben/trunk/etc/deploy.ini.in	(revision 8019)
+++ /main/waeup.uniben/trunk/etc/deploy.ini.in	(revision 8020)
@@ -48,5 +48,5 @@
 
 [app:main]
-use = egg:waeup.custom
+use = egg:waeup.uniben
 filter-with = translogger
 
Index: /main/waeup.uniben/trunk/etc/site.zcml.in
===================================================================
--- /main/waeup.uniben/trunk/etc/site.zcml.in	(revision 8019)
+++ /main/waeup.uniben/trunk/etc/site.zcml.in	(revision 8020)
@@ -1,8 +1,8 @@
 <configure xmlns="http://namespaces.zope.org/zope"
            xmlns:kofa="http://namespaces.waeup.org/kofa"
-           i18n_domain="waeup.custom">
+           i18n_domain="waeup.uniben">
 
-  <include package="waeup.custom" />
-  <include package="waeup.custom" file="mail.zcml" />
+  <include package="waeup.uniben" />
+  <include package="waeup.uniben" file="mail.zcml" />
 
   <!-- Where should the datacenter reside by default? -->
@@ -10,5 +10,5 @@
       path="${buildout:directory}/var/datacenter" />
 
-    <configure i18n_domain="waeup.custom">
+    <configure i18n_domain="waeup.uniben">
 
       <unauthenticatedPrincipal id="zope.anybody"
Index: /main/waeup.uniben/trunk/setup.py
===================================================================
--- /main/waeup.uniben/trunk/setup.py	(revision 8019)
+++ /main/waeup.uniben/trunk/setup.py	(revision 8020)
@@ -43,5 +43,5 @@
     read('README.txt')
     + '\n\n'
-    + read('src', 'waeup', 'custom', 'README.txt')
+    + read('src', 'waeup', 'uniben', 'README.txt')
     + '\n\n'
     + read('CHANGES.txt')
@@ -51,5 +51,5 @@
     )
 
-setup(name = 'waeup.custom',
+setup(name = 'waeup.uniben',
       version = version,
       description = "A customized waeup.kofa",
@@ -89,5 +89,5 @@
       # Add entry points here
       [hurry.resource.libraries]
-      waeup_custom = waeup.custom.browser.resources:waeup_custom
+      waeup_uniben = waeup.uniben.browser.resources:waeup_uniben
       [console_scripts]
       kofa-debug = grokcore.startup:interactive_debug_prompt
Index: /main/waeup.uniben/trunk/src/waeup/uniben/README.txt
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/README.txt	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/README.txt	(revision 8020)
@@ -0,0 +1,4 @@
+The waeup.uniben package
+************************
+
+A customized portal software for student registration.
Index: /main/waeup.uniben/trunk/src/waeup/uniben/__init__.py
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/__init__.py	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/__init__.py	(revision 8020)
@@ -0,0 +1,1 @@
+# Make this a package.
Index: /main/waeup.uniben/trunk/src/waeup/uniben/applicants/__init__.py
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/applicants/__init__.py	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/applicants/__init__.py	(revision 8020)
@@ -0,0 +1,1 @@
+# Make this a package.
Index: /main/waeup.uniben/trunk/src/waeup/uniben/applicants/applicant.py
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/applicants/applicant.py	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/applicants/applicant.py	(revision 8020)
@@ -0,0 +1,46 @@
+## $Id$
+##
+## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+
+import grok
+from zope.interface import implementedBy
+from waeup.kofa.applicants.applicant import Applicant, ApplicantFactory
+from waeup.kofa.applicants.interfaces import IApplicantEdit
+from waeup.kofa.utils.helpers import attrs_to_fields
+from waeup.uniben.applicants.interfaces import(
+    IApplicant, IUGApplicantEdit, IPGApplicantEdit)
+
+class Applicant(Applicant):
+
+    grok.implements(IApplicant, IUGApplicantEdit, IPGApplicantEdit)
+    grok.provides(IApplicant)
+
+# Set all attributes of Applicant required in IApplicant as field
+# properties. Doing this, we do not have to set initial attributes
+# ourselves and as a bonus we get free validation when an attribute is
+# set.
+Applicant = attrs_to_fields(Applicant)
+
+class ApplicantFactory(ApplicantFactory):
+    """A factory for customized applicants.
+    """
+
+    def __call__(self, *args, **kw):
+        return Applicant()
+
+    def getInterfaces(self):
+        return implementedBy(Applicant)
Index: /main/waeup.uniben/trunk/src/waeup/uniben/applicants/browser.py
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/applicants/browser.py	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/applicants/browser.py	(revision 8020)
@@ -0,0 +1,120 @@
+## $Id$
+##
+## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+"""UI components for basic applicants and related components.
+"""
+import grok
+from waeup.kofa.widgets.datewidget import (
+    FriendlyDateWidget, FriendlyDateDisplayWidget,
+    FriendlyDatetimeDisplayWidget)
+from waeup.kofa.widgets.phonewidget import PhoneWidget
+from waeup.kofa.applicants.browser import (ApplicantDisplayFormPage,
+    OnlinePaymentCallbackPage, ExportPDFPage,
+    ApplicantManageFormPage, ApplicantEditFormPage)
+from waeup.kofa.applicants.viewlets import RequestCallbackActionButton
+from waeup.kofa.applicants.pdf import PDFApplicationSlip
+#from waeup.kofa.applicants.interfaces import IApplicant, IApplicantEdit
+from waeup.uniben.applicants.interfaces import (
+    IPGApplicant, IUGApplicant, IPGApplicantEdit, IUGApplicantEdit)
+from waeup.uniben.interfaces import MessageFactory as _
+
+class RequestCallbackActionButton(RequestCallbackActionButton):
+    """ Do not display the base package callback button in custom pages.
+    """
+    @property
+    def target_url(self):
+        return ''
+
+class OnlinePaymentCallbackPage(OnlinePaymentCallbackPage):
+    """ Neutralize callback simulation view
+    """
+    def update(self):
+        return
+
+class PDFApplicationSlip(PDFApplicationSlip):
+
+    @property
+    def form_fields(self):
+        target = getattr(self.context.__parent__, 'prefix', None)
+        if target is not None and target.startswith('pg'):
+            form_fields = grok.AutoFields(IPGApplicant).omit(
+                'locked', 'course_admitted')
+        else:
+            form_fields = grok.AutoFields(IUGApplicant).omit(
+                'locked', 'course_admitted')
+        form_fields[
+            'date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
+        return form_fields
+
+class ApplicantDisplayFormPage(ApplicantDisplayFormPage):
+    """A display view for applicant data.
+    """
+
+    @property
+    def form_fields(self):
+        target = getattr(self.context.__parent__, 'prefix', None)
+        if target is not None and target.startswith('pg'):
+            form_fields = grok.AutoFields(IPGApplicant).omit(
+                'locked', 'course_admitted', 'password')
+        else:
+            form_fields = grok.AutoFields(IUGApplicant).omit(
+                'locked', 'course_admitted', 'password')
+        form_fields[
+            'date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
+        return form_fields
+
+class ApplicantManageFormPage(ApplicantManageFormPage):
+    """A full edit view for applicant data.
+    """
+    
+    @property
+    def form_fields(self):
+        target = getattr(self.context.__parent__, 'prefix', None)
+        if target is not None and target.startswith('pg'):
+            form_fields = grok.AutoFields(IPGApplicant)
+        else:
+            form_fields = grok.AutoFields(IUGApplicant)
+        form_fields[
+            'date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
+        form_fields['phone'].custom_widget = PhoneWidget
+        form_fields['student_id'].for_display = True
+        form_fields['applicant_id'].for_display = True
+        form_fields['phone'].custom_widget = PhoneWidget
+        return form_fields
+
+class ApplicantEditFormPage(ApplicantEditFormPage):
+    """An applicant-centered edit view for applicant data.
+    """
+
+    @property
+    def form_fields(self):
+        target = getattr(self.context.__parent__, 'prefix', None)
+        if target is not None and target.startswith('pg'):
+            form_fields = grok.AutoFields(IPGApplicantEdit).omit(
+                'locked', 'course_admitted', 'student_id',
+                'screening_score', 'reg_number'
+                )
+        else:
+            form_fields = grok.AutoFields(IUGApplicantEdit).omit(
+                'locked', 'course_admitted', 'student_id',
+                'screening_score', 'reg_number'
+                )
+        form_fields[
+            'date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
+        form_fields['phone'].custom_widget = PhoneWidget
+        form_fields['applicant_id'].for_display = True
+        return form_fields
Index: /main/waeup.uniben/trunk/src/waeup/uniben/applicants/interfaces.py
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/applicants/interfaces.py	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/applicants/interfaces.py	(revision 8020)
@@ -0,0 +1,68 @@
+## $Id$
+##
+## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+"""Customized interfaces of the university application package.
+"""
+
+from zope import schema
+from waeup.kofa.applicants.interfaces import IApplicant, IApplicantProcessData
+from waeup.uniben.interfaces import MessageFactory as _
+
+class IUGApplicant(IApplicant):
+    """An undergraduate applicant.
+
+    """
+
+class IPGApplicant(IApplicant):
+    """A postgraduate applicant.
+
+    """
+
+    employer = schema.TextLine(
+        title = _(u'Employer'),
+        required = False,
+        readonly = False,
+        )
+
+class IApplicant(IUGApplicant,IPGApplicant):
+    """An interface for both types of applicants.
+
+    """
+
+class IUGApplicantEdit(IUGApplicant):
+    """An undergraduate applicant interface for editing.
+
+    Here we can repeat the fields from base data and set the
+    `required` and `readonly` attributes to True to further restrict
+    the data access. Or we can allow only certain certificates to be
+    selected by choosing the appropriate source.
+
+    We cannot omit fields here. This has to be done in the
+    respective form page.
+    """
+
+class IPGApplicantEdit(IPGApplicant):
+    """A postgraduate applicant interface for editing.
+
+    Here we can repeat the fields from base data and set the
+    `required` and `readonly` attributes to True to further restrict
+    the data access. Or we can allow only certain certificates to be
+    selected by choosing the appropriate source.
+
+    We cannot omit fields here. This has to be done in the
+    respective form page.
+    """
Index: /main/waeup.uniben/trunk/src/waeup/uniben/applicants/tests.py
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/applicants/tests.py	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/applicants/tests.py	(revision 8020)
@@ -0,0 +1,135 @@
+## $Id$
+##
+## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+import os
+import shutil
+import tempfile
+from zope.interface.verify import verifyClass, verifyObject
+from zope.component.hooks import setSite, clearSite
+from zope.component import createObject
+from zope.testbrowser.testing import Browser
+from waeup.kofa.app import University
+from waeup.kofa.university.faculty import Faculty
+from waeup.kofa.university.department import Department
+from waeup.kofa.testing import FunctionalTestCase
+from waeup.kofa.applicants.container import ApplicantsContainer
+from waeup.uniben.testing import FunctionalLayer
+from waeup.kofa.interfaces import IBatchProcessor
+
+class ApplicantUITest(FunctionalTestCase):
+    """Perform some browser tests.
+    """
+    layer = FunctionalLayer
+
+    def setUp(self):
+        super(ApplicantUITest, self).setUp()
+        # Setup a sample site for each test
+        app = University()
+        self.dc_root = tempfile.mkdtemp()
+        app['datacenter'].setStoragePath(self.dc_root)
+
+        # Prepopulate the ZODB...
+        self.getRootFolder()['app'] = app
+        # we add the site immediately after creation to the
+        # ZODB. Catalogs and other local utilities are not setup
+        # before that step.
+        self.app = self.getRootFolder()['app']
+        # Set site here. Some of the following setup code might need
+        # to access grok.getSite() and should get our new app then
+        setSite(app)
+
+        # Add an two different applicants containers
+        self.pgcontainer = ApplicantsContainer()
+        self.pgcontainer.code = u'pg2011'
+        self.pgcontainer.prefix = u'pg'
+        self.app['applicants']['pg2011'] = self.pgcontainer
+        self.ugcontainer = ApplicantsContainer()
+        self.ugcontainer.code = u'app2011'
+        self.ugcontainer.prefix = u'app'
+        self.app['applicants']['app2011'] = self.ugcontainer
+
+        # Populate university
+        self.certificate = createObject('waeup.Certificate')
+        self.certificate.code = 'CERT1'
+        self.certificate.application_category = 'basic'
+        self.certificate.start_level = 100
+        self.certificate.end_level = 500
+        self.app['faculties']['fac1'] = Faculty()
+        self.app['faculties']['fac1']['dep1'] = Department()
+        self.app['faculties']['fac1']['dep1'].certificates.addCertificate(
+            self.certificate)
+
+        # Add (customized) applicants
+        pgapplicant = createObject(u'waeup.Applicant')
+        pgapplicant.firstname = u'Anna'
+        pgapplicant.lastname = u'Post'
+        self.app['applicants']['pg2011'].addApplicant(pgapplicant)
+        self.pgapplication_number = pgapplicant.application_number
+        self.pgapplicant = self.app['applicants']['pg2011'][
+            self.pgapplication_number]
+
+        ugapplicant = createObject(u'waeup.Applicant')
+        ugapplicant.firstname = u'Klaus'
+        ugapplicant.lastname = u'Under'
+        self.app['applicants']['app2011'].addApplicant(ugapplicant)
+        self.ugapplication_number = ugapplicant.application_number
+        self.ugapplicant = self.app['applicants']['app2011'][
+            self.ugapplication_number]
+
+        self.browser = Browser()
+        self.browser.handleErrors = False
+
+    def tearDown(self):
+        super(ApplicantUITest, self).tearDown()
+        shutil.rmtree(self.dc_root)
+        clearSite()
+        return
+
+    def test_manage_and_view_applicant(self):
+        # Managers can manage pg applicants
+        pgapplicant_path = ('http://localhost/app/applicants/pg2011/%s'
+            % self.pgapplication_number)
+        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
+        # The IPGApplicant interface is really used in all pages
+        self.browser.open(pgapplicant_path)
+        self.assertEqual(self.browser.headers['Status'], '200 Ok')
+        self.assertTrue('Employer' in self.browser.contents)
+        self.browser.open(pgapplicant_path + '/manage')
+        self.assertEqual(self.browser.headers['Status'], '200 Ok')
+        self.assertTrue('Employer' in self.browser.contents)
+        self.browser.open(pgapplicant_path + '/edit')
+        self.assertEqual(self.browser.headers['Status'], '200 Ok')
+        self.assertTrue('Employer' in self.browser.contents)
+        self.browser.open(pgapplicant_path + '/application_slip.pdf')
+        self.assertEqual(self.browser.headers['Status'], '200 Ok')
+        # If we lool at the applicant in the ug container,
+        # the employer field doesn't appear
+        ugapplicant_path = ('http://localhost/app/applicants/app2011/%s'
+            % self.ugapplication_number)
+        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
+        self.browser.open(ugapplicant_path)
+        self.assertEqual(self.browser.headers['Status'], '200 Ok')
+        self.assertFalse('Employer' in self.browser.contents)
+        self.browser.open(ugapplicant_path + '/manage')
+        self.assertEqual(self.browser.headers['Status'], '200 Ok')
+        self.assertFalse('Employer' in self.browser.contents)
+        self.browser.open(ugapplicant_path + '/edit')
+        self.assertEqual(self.browser.headers['Status'], '200 Ok')
+        self.assertFalse('Employer' in self.browser.contents)
+        self.browser.open(ugapplicant_path + '/application_slip.pdf')
+        self.assertEqual(self.browser.headers['Status'], '200 Ok')
+        return
Index: /main/waeup.uniben/trunk/src/waeup/uniben/applicants/utils.py
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/applicants/utils.py	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/applicants/utils.py	(revision 8020)
@@ -0,0 +1,40 @@
+## $Id$
+##
+## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+"""General helper functions and utilities for the application section.
+"""
+
+import grok
+from waeup.kofa.applicants.utils import ApplicantsUtils
+
+class ApplicantsUtils(ApplicantsUtils):
+    """A collection of parameters and methods subject to customization.
+    """
+
+    APP_TYPES_DICT = {
+        'pude': ['Post UDE Screening', 'PUDE'],
+        'prence': ['Pre-NCE Programme', 'PRE'],
+        'sandwich': ['Part Time Degree in Education', 'SAND'],
+        'pt': ['Part-Time Degree Programmes', 'PTP'],
+        'pume': ['Post UME Screening Test', 'PUME'],
+        'app': ['General Studies', 'APP'],
+        'cest': ['Common Entry Screening Test', 'CEST'],
+        'ct': ['Certificate Programmes', 'CTP'],
+        'dp': ['Diploma Programmes', 'DPP'],
+        'pce': ['PCE Screening', 'PCE'],
+        'pg': ['Postgraduate Programmes', 'PCE']
+        }
Index: /main/waeup.uniben/trunk/src/waeup/uniben/browser/__init__.py
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/browser/__init__.py	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/browser/__init__.py	(revision 8020)
@@ -0,0 +1,1 @@
+# Make this a package.
Index: /main/waeup.uniben/trunk/src/waeup/uniben/browser/pages.py
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/browser/pages.py	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/browser/pages.py	(revision 8020)
@@ -0,0 +1,32 @@
+## $Id$
+##
+## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+import grok
+from waeup.kofa.browser.pages import (
+    SessionConfigurationAddFormPage, SessionConfigurationManageFormPage)
+from waeup.uniben.interfaces import (
+    ISessionConfiguration, ISessionConfigurationAdd)
+
+class SessionConfigurationAddFormPage(SessionConfigurationAddFormPage):
+    """Add a session configuration object to configuration container.
+    """
+    form_fields = grok.AutoFields(ISessionConfigurationAdd)
+
+class SessionConfigurationManageFormPage(SessionConfigurationManageFormPage):
+    """Manage session configuration object.
+    """
+    form_fields = grok.AutoFields(ISessionConfiguration)
Index: /main/waeup.uniben/trunk/src/waeup/uniben/browser/resources.py
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/browser/resources.py	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/browser/resources.py	(revision 8020)
@@ -0,0 +1,29 @@
+## $Id$
+##
+## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+import grok
+from waeup.kofa.browser.resources import (
+    Library, ResourceInclusion, waeup_base_css,
+    )
+
+#: All local resources are registered under the name ``waeup_custom``.
+waeup_uniben = Library('waeup_uniben', 'static')
+
+custom_theme_1 = ResourceInclusion(
+    waeup_uniben, 'custom_theme_1.css',
+    depends = [waeup_base_css])
+
Index: /main/waeup.uniben/trunk/src/waeup/uniben/browser/tests.py
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/browser/tests.py	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/browser/tests.py	(revision 8020)
@@ -0,0 +1,57 @@
+## $Id$
+##
+## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+import tempfile
+import shutil
+from zope.testbrowser.testing import Browser
+from zope.component.hooks import setSite, clearSite
+from waeup.kofa.testing import FunctionalTestCase
+from waeup.uniben.testing import FunctionalLayer
+from waeup.kofa.app import University
+
+class CustomPackageBrowserTests(FunctionalTestCase):
+
+    layer = FunctionalLayer
+
+    def setUp(self):
+        super(CustomPackageBrowserTests, self).setUp()
+
+        # Setup a sample site for each test
+        app = University()
+        self.dc_root = tempfile.mkdtemp()
+        app['datacenter'].setStoragePath(self.dc_root)
+
+        # Put the prepopulated site into test ZODB and prepare test
+        # browser
+        self.getRootFolder()['app'] = app
+        setSite(app)
+        self.browser = Browser()
+        self.browser.handleErrors = False
+
+    def tearDown(self):
+        super(CustomPackageBrowserTests, self).tearDown()
+        clearSite()
+        shutil.rmtree(self.dc_root)
+
+    def test_custom_theme(self):
+        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
+        self.browser.open('http://localhost/app/configuration')
+        self.assertMatches('...Custom Theme 1...', self.browser.contents)
+        self.browser.getControl(name="form.skin").value = ['custom theme 1']
+        self.browser.getControl("Save").click()
+        self.browser.open('http://localhost/app/configuration')
+        return
Index: /main/waeup.uniben/trunk/src/waeup/uniben/browser/theming.py
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/browser/theming.py	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/browser/theming.py	(revision 8020)
@@ -0,0 +1,33 @@
+## $Id$
+##
+## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+import grok
+from waeup.kofa.browser.interfaces import ITheme
+from waeup.uniben.browser.resources import custom_theme_1
+
+from waeup.uniben.interfaces import MessageFactory as _
+
+class CustomTheme1(grok.GlobalUtility):
+    """A custom theme based on the Kofa base theme.
+    """
+    grok.implements(ITheme)
+    grok.name('custom theme 1')
+
+    description = _(u'Custom Theme 1')
+
+    def getResources(self):
+        return [custom_theme_1]
Index: /main/waeup.uniben/trunk/src/waeup/uniben/configuration.py
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/configuration.py	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/configuration.py	(revision 8020)
@@ -0,0 +1,47 @@
+## $Id$
+##
+## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+"""
+Containers for session configuration objects.
+"""
+import grok
+from zope.interface import implementedBy
+from waeup.kofa.utils.helpers import attrs_to_fields
+from waeup.kofa.configuration import (
+    SessionConfigurationFactory, SessionConfiguration)
+from waeup.uniben.interfaces import (
+    ISessionConfiguration, ISessionConfigurationAdd)
+
+
+class SessionConfiguration(SessionConfiguration):
+    """
+    Customized session configuration model
+    """
+
+    grok.implements(ISessionConfiguration, ISessionConfigurationAdd)
+
+SessionConfiguration = attrs_to_fields(SessionConfiguration)
+
+class SessionConfigurationFactory(SessionConfigurationFactory):
+    """A customized factory for session configuration objects.
+    """
+
+    def __call__(self, *args, **kw):
+        return SessionConfiguration(*args, **kw)
+
+    def getInterfaces(self):
+        return implementedBy(SessionConfiguration)
Index: /main/waeup.uniben/trunk/src/waeup/uniben/configure.zcml
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/configure.zcml	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/configure.zcml	(revision 8020)
@@ -0,0 +1,7 @@
+<configure xmlns="http://namespaces.zope.org/zope"
+           xmlns:grok="http://namespaces.zope.org/grok"
+           xmlns:i18n="http://namespaces.zope.org/i18n">
+  <include package="grok" />
+  <i18n:registerTranslations directory="locales" />
+  <includeOverrides package="waeup.uniben" file="overrides.zcml" />
+</configure>
Index: /main/waeup.uniben/trunk/src/waeup/uniben/etranzact/__init__.py
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/etranzact/__init__.py	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/etranzact/__init__.py	(revision 8020)
@@ -0,0 +1,1 @@
+# Make this a package.
Index: /main/waeup.uniben/trunk/src/waeup/uniben/etranzact/browser.py
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/etranzact/browser.py	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/etranzact/browser.py	(revision 8020)
@@ -0,0 +1,130 @@
+## $Id$
+##
+## Copyright (C) 2012 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+from datetime import datetime
+import httplib
+import urllib
+from xml.dom.minidom import parseString
+import grok
+from waeup.kofa.browser.layout import KofaPage, UtilityView
+from waeup.kofa.students.interfaces import IStudentOnlinePayment
+from waeup.kofa.students.browser import write_log_message
+from waeup.kofa.students.viewlets import RequestCallbackActionButton
+from waeup.uniben.interfaces import MessageFactory as _
+from waeup.uniben.utils.utils import actions_after_payment
+
+TERMINAL_ID = '0000000001'
+QUERY_URL =   'http://demo.etranzact.com:8080/WebConnect/queryPayoutletTransaction.jsp'
+
+class EtranzactEnterPinActionButton(RequestCallbackActionButton):
+    grok.order(5)
+    icon = 'actionicon_call.png'
+    text = _('Query eTranzact History')
+    target = 'enterpin'
+
+class EtranzactEnterPinPage(KofaPage):
+    """
+    """
+    grok.context(IStudentOnlinePayment)
+    grok.name('enterpin')
+    grok.template('enterpin')
+    grok.require('waeup.payStudent')
+
+    buttonname = _('Submit to eTranzact')
+    label = _('Requery eTranzact History')
+    action = 'query_history'
+
+class EtranzactQueryHistoryPage(UtilityView, grok.View):
+    """ Query history of eTranzact payments
+    """
+    grok.context(IStudentOnlinePayment)
+    grok.name('query_history')
+    grok.require('waeup.payStudent')
+
+    def update(self, confirmation_number=None):
+        if self.context.p_state == 'paid':
+            self.flash(_('This ticket has already been paid.'))
+            return
+        student = self.context.getStudent()
+        postdict = {}
+        postdict['TERMINAL_ID'] = TERMINAL_ID
+        #postdict['RESPONSE_URL'] = 'http://dummy'
+        postdict['CONFIRMATION_NO'] = confirmation_number
+        data = urllib.urlencode(postdict)
+        try:
+            f = urllib.urlopen(url=QUERY_URL, data=data)
+            success = f.read()
+            if '-1' in success:
+                self.flash(_('Unsuccessful callback'))
+                write_log_message(self,'unsuccessful callback: %s' % self.context.p_id)
+                self.context.p_state = 'failed'
+                return
+            success = success.replace('%20',' ').split('&')
+            # We expect at least two parameters
+            if len(success) < 2:
+                self.flash(_('Invalid callback: ${a}',
+                    mapping = {'a': success}))
+                write_log_message(self,'invalid callback: %s' % self.context.p_id)
+                self.context.p_state = 'failed'
+                return
+            try:
+                success_dict = dict([tuple(i.split('=')) for i in success])
+            except ValueError:
+                self.flash(_('Invalid callback: ${a}',
+                    mapping = {'a': success}))
+                write_log_message(self,'invalid callback: %s' % self.context.p_id)
+                self.context.p_state = 'failed'
+                return
+        except IOError:
+            self.flash(_('eTranzact IOError'))
+            return
+        write_log_message(self,'callback received: %s' % success)
+
+        self.context.r_code = u'ET'
+        self.context.r_desc = u'%s' % success_dict.get('TRANS_DESCR')
+        self.context.r_amount_approved = float(success_dict.get('TRANS_AMOUNT',0.0))
+        self.context.r_card_num = None
+        self.context.r_pay_reference = u'%s' % success_dict.get('RECEIPT_NO')
+        
+        if self.context.r_amount_approved != self.context.amount_auth:
+            self.flash(_('Wrong amount'))
+            write_log_message(
+                self,'successful callback but wrong amount: %s'
+                % self.context.p_id)
+            self.context.p_state = 'failed'
+            return
+
+        if success_dict.get('PAYMENT_CODE') != self.context.p_id:
+            self.flash(_('Wrong transaction id'))
+            write_log_message(
+                self,'successful callback but wrong transaction id: %s'
+                % self.context.p_id)
+            self.context.p_state = 'failed'
+            return
+
+        write_log_message(self,'successful callback: %s' % self.context.p_id)
+
+        self.context.p_state = 'paid'
+        self.context.payment_date = datetime.now()
+
+        actions_after_payment(student, self.context, self)
+
+        return
+
+    def render(self):
+        self.redirect(self.url(self.context, '@@index'))
+        return
Index: /main/waeup.uniben/trunk/src/waeup/uniben/etranzact/browser_templates/enterpin.pt
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/etranzact/browser_templates/enterpin.pt	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/etranzact/browser_templates/enterpin.pt	(revision 8020)
@@ -0,0 +1,22 @@
+<form tal:attributes="action view/action"
+    method="POST" i18n:domain="waeup.uniben">
+  <table class="form-table">
+    <tbody>
+      <tr>
+        <td i18n:translate="">
+        Confirmation Number (PIN):
+        </td>
+        <td>
+        <input name="confirmation_number" type="text" class="span5" maxlength=""
+               value="" />
+        </td>
+      </tr>
+      <tr>
+        <td colspan="2">
+          <input type="submit" name="SUBMIT" class="btn primary"
+                 tal:attributes="value view/buttonname" />
+        </td>
+      </tr>
+    </tbody>
+  </table>
+</form>
Index: /main/waeup.uniben/trunk/src/waeup/uniben/etranzact/tests.py
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/etranzact/tests.py	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/etranzact/tests.py	(revision 8020)
@@ -0,0 +1,82 @@
+## $Id$
+##
+## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+from hurry.workflow.interfaces import IWorkflowState
+from waeup.kofa.students.tests.test_browser import StudentsFullSetup
+from waeup.uniben.testing import FunctionalLayer
+
+
+# Also run tests that send requests to external servers?
+#   If you enable this, please make sure the external services
+#   do exist really and are not bothered by being spammed by a test programme.
+EXTERNAL_TESTS = False
+
+def external_test(func):
+    if not EXTERNAL_TESTS:
+        myself = __file__
+        if myself.endswith('.pyc'):
+            myself = myself[:-2]
+        print "WARNING: external tests are skipped!"
+        print "WARNING: edit %s to enable them." % myself
+        return
+    return func
+
+class EtranzactTests(StudentsFullSetup):
+    """Tests for the eTranzact payment gateway.
+    """
+
+    layer = FunctionalLayer
+
+    CONFIRMATION_NO = '500856521315472785095'
+
+    def setUp(self):
+        super(EtranzactTests, self).setUp()
+        # Managers can add online payment tickets
+        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
+        self.browser.open(self.payments_path)
+        IWorkflowState(self.student).setState('cleared')
+        self.browser.open(self.payments_path + '/addop')
+        self.browser.getControl("Create ticket").click()
+        self.assertMatches('...ticket created...',
+                           self.browser.contents)
+        ctrl = self.browser.getControl(name='val_id')
+        value = ctrl.options[0]
+        self.browser.getLink(value).click()
+        self.assertMatches('...Amount Authorized...',
+                           self.browser.contents)
+        self.payment_url = self.browser.url
+
+    def test_enterpin(self):
+        self.browser.getLink("Query eTranzact History").click()
+        self.assertMatches(
+            '...Confirmation Number (PIN):...',
+            self.browser.contents)
+
+    @external_test
+    def test_etranzact_query_history(self):
+
+        self.browser.open(self.payment_url + '/query_history?confirmation_number=%s'
+            % self.CONFIRMATION_NO)
+        self.assertMatches('...Wrong amount...',
+                          self.browser.contents)
+
+        self.student['payments'][value].amount_auth = self.student[
+            'payments'][value].r_amount_approved
+        self.browser.open(self.payment_url + '/query_history?confirmation_number=%s'
+            % self.CONFIRMATION_NO)
+        self.assertMatches('...Wrong transaction id...',
+                          self.browser.contents)
Index: /main/waeup.uniben/trunk/src/waeup/uniben/ftesting.zcml
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/ftesting.zcml	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/ftesting.zcml	(revision 8020)
@@ -0,0 +1,68 @@
+<configure
+   xmlns="http://namespaces.zope.org/zope"
+   xmlns:kofa="http://namespaces.waeup.org/kofa"
+   i18n_domain="waeup.uniben"
+   package="waeup.uniben"
+   >
+
+  <include package="grok" />
+  <includeOverrides package="waeup.uniben" />
+  <includeOverrides package="waeup.uniben" file="mail.zcml"/>
+
+  <!-- Where should the datacenter reside by default? -->
+  <kofa:datacenter
+      path="../../../parts/test/datacenter" />
+
+  <!-- Typical functional testing security setup -->
+  <securityPolicy
+      component="zope.securitypolicy.zopepolicy.ZopeSecurityPolicy"
+      />
+
+  <unauthenticatedPrincipal
+      id="zope.anybody"
+      title="Unauthenticated User"
+      />
+  <unauthenticatedGroup id="zope.Anybody"
+      title="Unauthenticated Users" />
+  <authenticatedGroup id="zope.Authenticated"
+      title="Authenticated Users" />
+  <everybodyGroup id="zope.Everybody"
+      title="All Users" />
+
+  <!-- grant
+      permission="zope.View"
+      principal="zope.Anybody"
+      / -->
+  <grant
+      permission="zope.app.dublincore.view"
+      principal="zope.Anybody" />
+  <grant
+      permission="zope.View"
+      principal="zope.Authenticated"
+      />
+  <grant
+      permission="zope.app.dublincore.view"
+      principal="zope.Authenticated"
+      />
+  <grant
+      permission="waeup.Public"
+      principal="zope.Everybody"
+      />
+  <grant 
+      permission="waeup.Anonymous"
+      principal="zope.Anybody"
+      />
+
+
+  <principal
+      id="zope.mgr"
+      title="Manager"
+      login="mgr"
+      password="mgrpw"
+      />
+
+  <role id="zope.Manager" title="Site Manager" />
+  <grantAll role="zope.Manager" />
+  <grant role="zope.Manager" principal="zope.mgr" />
+
+</configure>
Index: /main/waeup.uniben/trunk/src/waeup/uniben/interfaces.py
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/interfaces.py	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/interfaces.py	(revision 8020)
@@ -0,0 +1,113 @@
+## $Id$
+##
+## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+
+import zope.i18nmessageid
+from zope import schema
+from waeup.kofa.interfaces import (
+    ISessionConfiguration, academic_sessions_vocab)
+
+_ = MessageFactory = zope.i18nmessageid.MessageFactory('waeup.uniben')
+
+# It's recommended to replicate all fields from the base package here.
+class ISessionConfiguration(ISessionConfiguration):
+    """A session configuration object.
+    """
+
+    # Base fees, do not remove.
+
+    school_fee_base = schema.Float(
+        title = _(u'School Fee (ignored)'),
+        default = 0.0,
+        required = False,
+        )
+
+    surcharge_1 = schema.Float(
+        title = _(u'Surcharge Portal Provider'),
+        default = 0.0,
+        required = False,
+        )
+
+    surcharge_2 = schema.Float(
+        title = _(u'Surcharge Interswitch'),
+        default = 0.0,
+        required = False,
+        )
+
+    surcharge_3 = schema.Float(
+        title = _(u'Surcharge 3'),
+        default = 0.0,
+        required = False,
+        )
+
+    clearance_fee = schema.Float(
+        title = _(u'Clearance Fee'),
+        default = 0.0,
+        required = False,
+        )
+
+    booking_fee = schema.Float(
+        title = _(u'Booking Fee'),
+        default = 0.0,
+        required = False,
+        )
+
+    acceptance_fee = schema.Float(
+        title = _(u'Acceptance Fee'),
+        default = 0.0,
+        required = False,
+        )
+
+    # Additional fees in waeup.uniben
+
+    maint_fee = schema.Float(
+        title = _(u'Maintenance Fee'),
+        default = 0.0,
+        required = False,
+        )
+
+    gown = schema.Float(
+        title = _(u'Gown Fee'),
+        default = 0.0,
+        required = False,
+        )
+
+    transfer = schema.Float(
+        title = _(u'Transfer Fee'),
+        default = 0.0,
+        required = False,
+        )
+
+    def getSessionString():
+        """Returns the session string from the vocabulary.
+        """
+
+
+class ISessionConfigurationAdd(ISessionConfiguration):
+    """A session configuration object in add mode.
+    """
+
+    academic_session = schema.Choice(
+        title = _(u'Academic Session'),
+        source = academic_sessions_vocab,
+        default = None,
+        required = True,
+        readonly = False,
+        )
+
+ISessionConfigurationAdd['academic_session'].order =  ISessionConfiguration[
+    'academic_session'].order
Index: /main/waeup.uniben/trunk/src/waeup/uniben/interswitch/__init__.py
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/interswitch/__init__.py	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/interswitch/__init__.py	(revision 8020)
@@ -0,0 +1,1 @@
+# Make this a package.
Index: /main/waeup.uniben/trunk/src/waeup/uniben/interswitch/browser.py
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/interswitch/browser.py	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/interswitch/browser.py	(revision 8020)
@@ -0,0 +1,310 @@
+## $Id$
+##
+## Copyright (C) 2012 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+from datetime import datetime
+import httplib
+import urllib
+from xml.dom.minidom import parseString
+import grok
+from waeup.kofa.browser.layout import KofaPage, UtilityView
+from waeup.kofa.accesscodes import create_accesscode
+from waeup.kofa.students.interfaces import IStudentOnlinePayment
+from waeup.kofa.students.browser import write_log_message
+from waeup.kofa.students.viewlets import RequestCallbackActionButton
+from waeup.uniben.utils.utils import actions_after_payment
+from waeup.uniben.interfaces import MessageFactory as _
+
+PRODUCT_ID = '57'
+SITE_NAME = 'xyz.waeup.org'
+PROVIDER_ACCT = '2345'
+PROVIDER_BANK_ID = '8'
+PROVIDER_ITEM_NAME = 'Kofa Provider Fee'
+INSTITUTION_ACCT = '1234'
+INSTITUTION_BANK_ID = '9'
+INSTITUTION_NAME = 'Sample University'
+CURRENCY = '566'
+PAY_ITEM_ID = '5700'
+QUERY_URL =   'https://testwebpay.interswitchng.com/test_paydirect/services/TransactionQueryURL.aspx'
+POST_ACTION = 'https://testwebpay.interswitchng.com/test_paydirect/webpay/pay.aspx'
+
+HOST = 'testwebpay.interswitchng.com'
+URL = '/test_paydirect/services/TransactionQueryWs.asmx'
+httplib.HTTPConnection.debuglevel = 0
+
+def SOAP_post(soap_action,xml):
+    """Handles making the SOAP request.
+
+    Further reading:
+    http://testwebpay.interswitchng.com/test_paydirect/services/TransactionQueryWs.asmx?op=getTransactionData
+    """
+    h = httplib.HTTPConnection(HOST)
+    headers={
+        'Host':HOST,
+        'Content-Type':'text/xml; charset=utf-8',
+        'Content-Length':len(xml),
+        'SOAPAction':'"%s"' % soap_action,
+    }
+    h.request('POST', URL, body=xml,headers=headers)
+    r = h.getresponse()
+    d = r.read()
+    if r.status!=200:
+        raise ValueError('Error connecting: %s, %s' % (r.status, r.reason))
+    return d
+
+def get_SOAP_response(product_id, transref):
+    xml="""\
+<?xml version="1.0" encoding="utf-8"?>
+<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
+  <soap:Body>
+    <getTransactionData xmlns="http://tempuri.org/">
+      <product_id>%s</product_id>
+      <trans_ref>%s</trans_ref>
+    </getTransactionData>
+  </soap:Body>
+</soap:Envelope>""" % (product_id, transref)
+    result_xml=SOAP_post("http://tempuri.org/getTransactionData",xml)
+    doc=parseString(result_xml)
+    response=doc.getElementsByTagName('getTransactionDataResult')[0].firstChild.data
+    return response
+
+class InterswitchActionButton(RequestCallbackActionButton):
+    grok.order(2)
+    icon = 'actionicon_pay.png'
+    text = _('CollegePAY')
+    target = 'goto_interswitch'
+
+    @property
+    def target_url(self):
+        if self.context.p_state != 'unpaid':
+            return ''
+        return self.view.url(self.view.context, self.target)
+
+class InterswitchRequestCallbackActionButton(RequestCallbackActionButton):
+    grok.order(3)
+    icon = 'actionicon_call.png'
+    text = _('Request CollegePAY callback')
+
+    def target_url(self):
+        if self.context.p_state == 'paid':
+            return ''
+        site_redirect_url = self.view.url(self.view.context, 'isw_callback')
+        args = {
+            'transRef':self.context.p_id,
+            'prodID':PRODUCT_ID,
+            'redirectURL':site_redirect_url}
+        return QUERY_URL + '?%s' % urllib.urlencode(args)
+
+# Alternative preferred solution
+class InterswitchRequestWebserviceActionButton(RequestCallbackActionButton):
+    grok.order(4)
+    icon = 'actionicon_call.png'
+    text = _('Request CollegePAY webservice')
+    target = 'request_webservice'
+
+
+class InterswitchPage(KofaPage):
+    """ View which sends a POST request to the Interswitch
+    CollegePAY payment gateway.
+    """
+    grok.context(IStudentOnlinePayment)
+    grok.name('goto_interswitch')
+    grok.template('goto_interswitch')
+    grok.require('waeup.payStudent')
+    label = _('Submit data to CollegePAY (Interswitch Payment Gateway)')
+    submit_button = _('Submit')
+    action = POST_ACTION
+    site_name = SITE_NAME
+    currency = CURRENCY
+    pay_item_id = PAY_ITEM_ID
+    product_id = PRODUCT_ID
+
+    def update(self):
+        if self.context.p_state != 'unpaid':
+            self.flash(_("Payment ticket can't be re-send to CollegePAY."))
+            self.redirect(self.url(self.context, '@@index'))
+            return
+        self.student = self.context.getStudent()
+        self.amount = (self.context.amount_auth + self.context.surcharge_1 +
+            self.context.surcharge_2 + self.context.surcharge_3)
+        self.amount_100 = 100 * self.amount
+        self.local_date_time = str(self.context.creation_date)
+        self.site_redirect_url = self.url(self.context, 'isw_callback')
+        certificate = getattr(self.student['studycourse'],'certificate',None)
+        xmldict = {}
+        if certificate is not None:
+            xmldict['department'] = certificate.__parent__.__parent__.code
+            xmldict['faculty'] = certificate.__parent__.__parent__.__parent__.code
+        else:
+            xmldict['department'] = None
+            xmldict['faculty'] = None
+        xmldict['detail_ref'] = self.context.p_id
+        xmldict['provider_amt'] = 100 * self.context.surcharge_1
+        xmldict['provider_acct'] = PROVIDER_ACCT
+        xmldict['provider_bank_id'] = PROVIDER_BANK_ID
+        xmldict['provider_item_name'] = PROVIDER_ITEM_NAME
+        xmldict['institution_amt'] = 100 * self.context.amount_auth
+        xmldict['institution_acct'] = INSTITUTION_ACCT
+        xmldict['institution_bank_id'] = INSTITUTION_BANK_ID
+        xmldict['institution_item_name'] = self.context.p_category
+        xmldict['institution_name'] = INSTITUTION_NAME
+        # Interswitch amount is not part of the xml data
+        xmltext = """<payment_item_detail>
+<item_details detail_ref="%(detail_ref)s" college="%(institution_name)s" department="%(department)s" faculty="%(faculty)s">
+<item_detail item_id="1" item_name="%(institution_item_name)s" item_amt="%(institution_amt)d" bank_id="%(institution_bank_id)s" acct_num="%(institution_acct)s" />
+<item_detail item_id="2" item_name="%(provider_item_name)s" item_amt="%(provider_amt)d" bank_id="%(provider_bank_id)s" acct_num="%(provider_acct)s" />
+</item_details>
+</payment_item_detail>""" % xmldict
+        self.xml_data = """<input type="hidden" name="xml_data" value='%s'  />""" % xmltext
+        return
+
+class InterswitchPaymentCallbackPage(UtilityView, grok.View):
+    """ Callback view for the CollegePAY gateway
+    """
+    grok.context(IStudentOnlinePayment)
+    grok.name('isw_callback')
+    grok.require('waeup.payStudent')
+
+    # This view is not yet working for offline querying transactions
+    # since the query string differs from the query string sent after
+    # posting transactions. This Interswitch bug must be removed first.
+    # Alternatively, we could use the webservice only and replace
+    # the RequestCallbackActionButton by a RequestWebserviceActionButton
+
+    def update(self):
+        if self.context.p_state == 'paid':
+            self.flash(_('This ticket has already been paid.'))
+            return
+        student = self.context.getStudent()
+        query = self.request.form
+        write_log_message(self,'callback received: %s' % query)
+
+        self.context.r_card_num = query.get('cardNum', None)
+        self.context.r_code = query.get('resp', None)
+        self.context.r_pay_reference  = query.get('payRef', None)
+        self.context.r_amount_approved = float(query.get('apprAmt', '0.0')) / 100
+        self.context.r_desc = query.get('desc', None)
+
+        if self.context.r_code != '00':
+            self.flash(_('Unsuccessful callback: ${a}',
+                mapping = {'a': query.get('desc', _('Incomplete query string.'))}))
+            write_log_message(self,'unsuccessful callback: %s' % self.context.p_id)
+            self.context.p_state = 'failed'
+            return
+
+        total_amount_auth = (
+            self.context.amount_auth
+            + self.context.surcharge_1
+            + self.context.surcharge_2)
+
+        if self.context.r_amount_approved != total_amount_auth:
+            self.flash(_('Wrong amount'))
+            write_log_message(
+                self,'successful but wrong amount: %s' % self.context.p_id)
+            self.context.p_state = 'failed'
+            return
+
+        try:
+            validation_list = get_SOAP_response(
+                PRODUCT_ID, self.context.p_id).split(':')
+            # Validation does not make sense yet since the query string
+            # formats are conflicting. We are only printing the validation
+            # string, nothing else.
+            print 'WARNING: Webservice validation is not yet implemented'
+            print 'validation list: %s' % validation_list
+        except:
+            print 'Connection to webservice failed.'
+
+        # Add webservice validation here
+
+        write_log_message(self,'valid callback: %s' % self.context.p_id)
+        self.context.p_state = 'paid'
+        self.context.payment_date = datetime.now()
+        actions_after_payment(student, self.context, self)
+        return
+
+    def render(self):
+        self.redirect(self.url(self.context, '@@index'))
+        return
+
+# Alternative solution, replaces OnlinePaymentCallbackPage
+class InterswitchPaymentRequestWebservicePage(UtilityView, grok.View):
+    """ Request webservice view for the CollegePAY gateway
+    """
+    grok.context(IStudentOnlinePayment)
+    grok.name('request_webservice')
+    grok.require('waeup.payStudent')
+
+    def update(self):
+        if self.context.p_state == 'paid':
+            self.flash(_('This ticket has already been paid.'))
+            return
+        student = self.context.getStudent()
+        sr = get_SOAP_response(PRODUCT_ID, self.context.p_id)
+        write_log_message(self,'callback received: %s' % sr)
+        wlist = sr.split(':')
+        if len(wlist) != 7:
+            self.flash(_('Invalid callback: ${a}',
+                mapping = {'a': wlist}))
+            write_log_message(self,'invalid callback: %s' % self.context.p_id)
+            return
+        self.context.r_code = wlist[0]
+        self.context.r_desc = wlist[1]
+        self.context.r_amount_approved = float(wlist[2]) / 100
+        self.context.r_card_num = wlist[3]
+        self.context.r_pay_reference = wlist[5]
+        
+        if self.context.r_code != '00':
+            self.flash(_('Unsuccessful callback: ${a}',
+                mapping = {'a': wlist[1]}))
+            write_log_message(
+                self,'unsuccessful callback: %s' % self.context.p_id)
+            self.context.p_state = 'failed'
+            return
+
+        total_amount_auth = (
+            self.context.amount_auth
+            + self.context.surcharge_1
+            + self.context.surcharge_2)
+
+        if self.context.r_amount_approved != total_amount_auth:
+            self.flash(_('Wrong amount'))
+            write_log_message(
+                self,'successful callback but wrong amount: %s'
+                % self.context.p_id)
+            self.context.p_state = 'failed'
+            return
+
+        if wlist[4] != self.context.p_id:
+            self.flash(_('Wrong transaction id'))
+            write_log_message(
+                self,'successful callback but wrong transaction id: %s'
+                % self.context.p_id)
+            self.context.p_state = 'failed'
+            return
+
+        write_log_message(self,'successful callback: %s' % self.context.p_id)
+
+        self.context.p_state = 'paid'
+        self.context.payment_date = datetime.now()
+
+        actions_after_payment(student, self.context, self)
+
+        return
+
+    def render(self):
+        self.redirect(self.url(self.context, '@@index'))
+        return
Index: /main/waeup.uniben/trunk/src/waeup/uniben/interswitch/browser_templates/goto_interswitch.pt
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/interswitch/browser_templates/goto_interswitch.pt	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/interswitch/browser_templates/goto_interswitch.pt	(revision 8020)
@@ -0,0 +1,79 @@
+<form i18n:domain="waeup.uniben"
+  tal:attributes="action view/action" method="POST">
+
+  <p i18n:translate="">
+    These data will be submitted to the CollegePAY payment gateway,
+    please check:
+  </p>
+
+  <table class="form-table" i18n:domain="waeup.kofa">
+    <thead>
+    </thead>
+    <tbody>
+      <tr>
+        <td i18n:translate="" class="fieldname">
+          Total Amount Authorized:</td>
+        <td>
+          <span tal:replace="view/amount">AMOUNT</span>
+        </td>
+      </tr>
+      <tr>
+        <td i18n:translate="" class="fieldname">
+          Transaction Reference:</td>
+        <td>
+          <span tal:replace="view/context/p_id">TXNREF</span>
+        </td>
+      </tr>
+      <tr>
+        <td i18n:translate="" class="fieldname">
+          Customer Id:</td>
+        <td>
+          <span tal:replace="view/student/student_id">CUSTID</span>
+        </td>
+      </tr>
+      <tr>
+        <td i18n:translate="" class="fieldname">
+          Customer Name:</td>
+        <td>
+          <span tal:replace="view/student/display_fullname">CUSTNAME</span>
+        </td>
+      </tr>
+      <tr>
+        <td i18n:translate="" class="fieldname">
+          Payment Item:</td>
+        <td>
+          <span tal:replace="view/context/p_item">PAYMENTITEM</span>
+        </td>
+      </tr>
+      <tr>
+        <td i18n:translate="" class="fieldname">
+          Payment Category:</td>
+        <td>
+          <span tal:replace="view/context/p_category">PAYMENTCATEGORY</span>
+        </td>
+      </tr>
+    </tbody>
+  </table>
+
+  <input type="hidden" name="product_id" tal:attributes="value view/product_id" />
+  <input type="hidden" name="currency" tal:attributes="value view/currency" />
+  <input type="hidden" name="pay_item_id" tal:attributes="value view/pay_item_id" />
+  <input type="hidden" name="site_redirect_url" tal:attributes="value view/site_redirect_url" />
+  <input type="hidden" name="site_name" tal:attributes="value view/site_name" />
+  <input type="hidden" name="local_date_time" tal:attributes="value view/local_date_time" />
+  <input type="hidden" name="amount" tal:attributes="value view/amount_100" />
+
+  <input type="hidden" name="txn_ref" tal:attributes="value view/context/p_id" />
+  <input type="hidden" name="cust_id_desc" tal:attributes="value view/context/p_item" />
+  <input type="hidden" name="pay_item_name" tal:attributes="value view/context/p_category" />
+
+  <input type="hidden" name="cust_id" tal:attributes="value view/student/student_id" />
+  <input type="hidden" name="cust_name" tal:attributes="value view/student/display_fullname" />
+
+
+  <input type="hidden" name="payment_params" value="college_split" />
+  <span tal:replace="structure view/xml_data"></span>
+
+  <input type="submit" class="btn" tal:attributes="value view/submit_button"/>
+
+</form>
Index: /main/waeup.uniben/trunk/src/waeup/uniben/interswitch/tests.py
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/interswitch/tests.py	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/interswitch/tests.py	(revision 8020)
@@ -0,0 +1,108 @@
+## $Id$
+##
+## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+from hurry.workflow.interfaces import IWorkflowState
+from waeup.kofa.students.tests.test_browser import StudentsFullSetup
+from waeup.uniben.testing import FunctionalLayer
+
+# Also run tests that send requests to external servers?
+#   If you enable this, please make sure the external services
+#   do exist really and are not bothered by being spammed by a test programme.
+EXTERNAL_TESTS = False
+
+def external_test(func):
+    if not EXTERNAL_TESTS:
+        myself = __file__
+        if myself.endswith('.pyc'):
+            myself = myself[:-2]
+        print "WARNING: external tests are skipped!"
+        print "WARNING: edit %s to enable them." % myself
+        return
+    return func
+
+
+class InterswitchTests(StudentsFullSetup):
+    """Tests for the Interswitch payment gateway.
+    """
+
+    layer = FunctionalLayer
+
+    def setUp(self):
+        super(InterswitchTests, self).setUp()
+        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
+        self.browser.open(self.payments_path)
+        IWorkflowState(self.student).setState('cleared')
+        self.browser.open(self.payments_path + '/addop')
+        self.browser.getControl("Create ticket").click()
+        self.assertMatches('...ticket created...',
+                           self.browser.contents)
+        ctrl = self.browser.getControl(name='val_id')
+        value = ctrl.options[0]
+        self.browser.getLink(value).click()
+        self.assertMatches('...Amount Authorized...',
+                           self.browser.contents)
+        self.payment_url = self.browser.url
+
+
+    def callback_url(self, payment_url, resp, apprAmt):
+        return payment_url + (
+            '/isw_callback?echo=' +
+            '&resp=%s' +
+            '&desc=Something went wrong' +
+            '&txnRef=p1331792385335' +
+            '&payRef=' + '&retRef=' +
+            '&cardNum=0' +
+            '&apprAmt=%s' +
+            '&url=http://xyz') % (resp, apprAmt)
+
+    def test_interswitch_form(self):
+
+        # Manager can access InterswitchForm
+        self.browser.getLink("CollegePAY", index=0).click()
+        self.assertMatches('...Total Amount Authorized:...',
+                           self.browser.contents)
+        self.assertMatches(
+            '...<input type="hidden" name="amount" value="4000000.0" />...',
+            self.browser.contents)
+
+    @external_test
+    def test_callback(self):
+
+        # Manager can call callback manually
+        self.browser.open(self.callback_url(self.payment_url, 'XX', '300'))
+        self.assertMatches('...Unsuccessful callback: Something went wrong...',
+                          self.browser.contents)
+        self.assertMatches('...Failed...',
+                           self.browser.contents)
+        self.browser.open(self.payment_url + '/isw_callback')
+        self.assertMatches('...Unsuccessful callback: Incomplete query string...',
+                          self.browser.contents)
+        self.assertMatches('...Failed...',
+                           self.browser.contents)
+        self.browser.open(self.callback_url(self.payment_url, '00', '300000'))
+        self.assertMatches('...Wrong amount...',
+                          self.browser.contents)
+        self.browser.open(self.callback_url(self.payment_url, '00', '4000000'))
+        self.assertMatches('...Valid callback received...',
+                          self.browser.contents)
+
+    @external_test
+    def test_webservice(self):
+
+        self.browser.open(self.payment_url + '/request_webservice')
+        self.assertMatches('...Unsuccessful callback...',
+                          self.browser.contents)
Index: /main/waeup.uniben/trunk/src/waeup/uniben/locales/de/LC_MESSAGES/waeup.custom.po
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/locales/de/LC_MESSAGES/waeup.custom.po	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/locales/de/LC_MESSAGES/waeup.custom.po	(revision 8020)
@@ -0,0 +1,191 @@
+# #############################################################################
+#
+# Copyright (c) 2003-2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+# #############################################################################
+msgid ""
+msgstr ""
+"Project-Id-Version: WAeUP.Kofa\n"
+"POT-Creation-Date: Mon Apr  2 12:28:41 2012\n"
+"PO-Revision-Date: 2012-04-02 12:33+0100\n"
+"Last-Translator: Henrik Bettermann <henrik@waeup.org>\n"
+"Language-Team: WAeUP Germany <henrik@waeup.org>\n"
+"Language: de-DE\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: zope/app/locales/extract.py\n"
+"X-Poedit-Language: German\n"
+"X-Poedit-Country: GERMANY\n"
+"X-Poedit-SourceCharset: utf-8\n"
+
+#: waeup/custom/applicants/interfaces.py:36
+msgid "Employer"
+msgstr "Arbeitgeber"
+
+#: waeup/custom/browser/theming.py:30
+msgid "Custom Theme 1"
+msgstr "Benutzerdefiniertes Thema 1"
+
+#: waeup/custom/etranzact/browser.py:104
+#: waeup/custom/interswitch/browser.py:214
+#: waeup/custom/interswitch/browser.py:284
+msgid "Wrong amount"
+msgstr "Falscher Betrag"
+
+#: waeup/custom/etranzact/browser.py:112
+#: waeup/custom/interswitch/browser.py:292
+msgid "Wrong transaction id"
+msgstr "Falsche Transaktions-Id"
+
+#: waeup/custom/etranzact/browser.py:36
+msgid "Query eTranzact History"
+msgstr "Query eTranzact History"
+
+#: waeup/custom/etranzact/browser.py:47
+msgid "Submit to eTranzact"
+msgstr "Submit to eTranzact"
+
+#: waeup/custom/etranzact/browser.py:48
+msgid "Requery eTranzact History"
+msgstr "Requery eTranzact History"
+
+#: waeup/custom/etranzact/browser.py:60
+#: waeup/custom/interswitch/browser.py:189
+#: waeup/custom/interswitch/browser.py:253
+msgid "This ticket has already been paid."
+msgstr "Das Ticket wurde bereits bezahlt."
+
+#: waeup/custom/etranzact/browser.py:72
+msgid "Unsuccessful callback"
+msgstr "Erfolgloser Rückruf"
+
+#. Default: ""
+#: waeup/custom/etranzact/browser.py:79
+#: waeup/custom/etranzact/browser.py:87
+#: waeup/custom/interswitch/browser.py:260
+msgid "Invalid callback: ${a}"
+msgstr "Ungültiger Rückruf: ${a}"
+
+#: waeup/custom/etranzact/browser.py:93
+msgid "eTranzact IOError"
+msgstr "eTranzact IOError"
+
+#: waeup/custom/etranzact/browser_templates/enterpin.pt:6
+msgid "Confirmation Number (PIN):"
+msgstr "Confirmation Number (PIN):"
+
+#: waeup/custom/interfaces.py:105
+msgid "Academic Session"
+msgstr "Akademisches Jahr"
+
+#: waeup/custom/interfaces.py:34
+msgid "School Fee (ignored)"
+msgstr "Schulgebühr (wird nicht verwendet)"
+
+#: waeup/custom/interfaces.py:40
+msgid "Surcharge Portal Provider"
+msgstr "Gebühr Portal-Provider"
+
+#: waeup/custom/interfaces.py:46
+msgid "Surcharge Interswitch"
+msgstr "Gebühr Interswitch"
+
+#: waeup/custom/interfaces.py:52
+msgid "Surcharge 3"
+msgstr "Gebühr 3"
+
+#: waeup/custom/interfaces.py:58
+msgid "Clearance Fee"
+msgstr "Einschreibungsgebühr"
+
+#: waeup/custom/interfaces.py:64
+msgid "Booking Fee"
+msgstr "Buchungsgebühr"
+
+#: waeup/custom/interfaces.py:70
+msgid "Acceptance Fee"
+msgstr "Zulassungsgebühr"
+
+#: waeup/custom/interfaces.py:78
+msgid "Maintenance Fee"
+msgstr "Miete"
+
+#: waeup/custom/interfaces.py:84
+msgid "Gown Fee"
+msgstr "Talar-Gebühr"
+
+#: waeup/custom/interfaces.py:90
+msgid "Transfer Fee"
+msgstr "Studienfachwechselgebühr"
+
+#: waeup/custom/interswitch/browser.py:115
+msgid "Request CollegePAY webservice"
+msgstr "Request CollegePAY webservice"
+
+#: waeup/custom/interswitch/browser.py:127
+msgid "Submit data to CollegePAY (Interswitch Payment Gateway)"
+msgstr "Daten an CollegePAY (Interswitch Payment Gateway) senden"
+
+#: waeup/custom/interswitch/browser.py:128
+msgid "Submit"
+msgstr "Senden"
+
+#: waeup/custom/interswitch/browser.py:137
+msgid "Payment ticket can't be re-send to CollegePAY."
+msgstr "Bewertete Bezahl-Tickets können nicht erneut gesendet werden."
+
+#. Default: ""
+#: waeup/custom/interswitch/browser.py:202
+#: waeup/custom/interswitch/browser.py:271
+msgid "Unsuccessful callback: ${a}"
+msgstr "Erfolgloser Rückruf: ${a}"
+
+#: waeup/custom/interswitch/browser.py:203
+msgid "Incomplete query string."
+msgstr "Unvollständiger Suchtext"
+
+#: waeup/custom/interswitch/browser.py:87
+msgid "CollegePAY"
+msgstr "CollegePAY"
+
+#: waeup/custom/interswitch/browser.py:99
+msgid "Request CollegePAY callback"
+msgstr "CollegePAY Rückruf"
+
+#: waeup/custom/interswitch/browser_templates/goto_interswitch.pt:4
+msgid "These data will be submitted to the CollegePAY payment gateway, please check:"
+msgstr "These data will be submitted to the CollegePAY payment gateway, please check:"
+
+#: waeup/custom/students/utils.py:70
+msgid "Session configuration object is not available."
+msgstr "Session-Konfiguration ist nicht verfügbar."
+
+#: waeup/custom/students/utils.py:91
+msgid "Amount could not be determined."
+msgstr "Betrag konnte nicht ermittelt werden."
+
+#. Default: ""
+#: waeup/custom/utils/utils.py:30
+#: waeup/custom/utils/utils.py:38
+#: waeup/custom/utils/utils.py:46
+msgid "Valid callback received. ${a}"
+msgstr "Gültigen Rückruf erhalten. ${a}"
+
+#: waeup/custom/utils/utils.py:50
+msgid "Valid callback received."
+msgstr "Gültigen Rückruf erhalten."
+
+#~ msgid "Postgraduates"
+#~ msgstr "Postgraduierte"
+
+#~ msgid "handles postgraduate applicants"
+#~ msgstr "für Antragsteller von Postgraduierten-Studiengängen"
Index: /main/waeup.uniben/trunk/src/waeup/uniben/locales/en/LC_MESSAGES/waeup.custom.po
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/locales/en/LC_MESSAGES/waeup.custom.po	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/locales/en/LC_MESSAGES/waeup.custom.po	(revision 8020)
@@ -0,0 +1,181 @@
+# #############################################################################
+#
+# Copyright (c) 2003-2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+# #############################################################################
+msgid ""
+msgstr ""
+"Project-Id-Version: Development/Unknown\n"
+"POT-Creation-Date: Mon Apr  2 12:28:41 2012\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: Zope 3 Developers <zope3-dev@zope.org>\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: zope/app/locales/extract.py\n"
+
+#: waeup/custom/applicants/interfaces.py:36
+msgid "Employer"
+msgstr ""
+
+#: waeup/custom/browser/theming.py:30
+msgid "Custom Theme 1"
+msgstr ""
+
+#: waeup/custom/etranzact/browser.py:104
+#: waeup/custom/interswitch/browser.py:214
+#: waeup/custom/interswitch/browser.py:284
+msgid "Wrong amount"
+msgstr ""
+
+#: waeup/custom/etranzact/browser.py:112
+#: waeup/custom/interswitch/browser.py:292
+msgid "Wrong transaction id"
+msgstr ""
+
+#: waeup/custom/etranzact/browser.py:36
+msgid "Query eTranzact History"
+msgstr ""
+
+#: waeup/custom/etranzact/browser.py:47
+msgid "Submit to eTranzact"
+msgstr ""
+
+#: waeup/custom/etranzact/browser.py:48
+msgid "Requery eTranzact History"
+msgstr ""
+
+#: waeup/custom/etranzact/browser.py:60
+#: waeup/custom/interswitch/browser.py:189
+#: waeup/custom/interswitch/browser.py:253
+msgid "This ticket has already been paid."
+msgstr ""
+
+#: waeup/custom/etranzact/browser.py:72
+msgid "Unsuccessful callback"
+msgstr ""
+
+#. Default: ""
+#: waeup/custom/etranzact/browser.py:79 waeup/custom/etranzact/browser.py:87
+#: waeup/custom/interswitch/browser.py:260
+msgid "Invalid callback: ${a}"
+msgstr ""
+
+#: waeup/custom/etranzact/browser.py:93
+msgid "eTranzact IOError"
+msgstr ""
+
+#: waeup/custom/etranzact/browser_templates/enterpin.pt:6
+msgid "Confirmation Number (PIN):"
+msgstr ""
+
+#: waeup/custom/interfaces.py:105
+msgid "Academic Session"
+msgstr ""
+
+#: waeup/custom/interfaces.py:34
+msgid "School Fee (ignored)"
+msgstr ""
+
+#: waeup/custom/interfaces.py:40
+msgid "Surcharge Portal Provider"
+msgstr ""
+
+#: waeup/custom/interfaces.py:46
+msgid "Surcharge Interswitch"
+msgstr ""
+
+#: waeup/custom/interfaces.py:52
+msgid "Surcharge 3"
+msgstr ""
+
+#: waeup/custom/interfaces.py:58
+msgid "Clearance Fee"
+msgstr ""
+
+#: waeup/custom/interfaces.py:64
+msgid "Booking Fee"
+msgstr ""
+
+#: waeup/custom/interfaces.py:70
+msgid "Acceptance Fee"
+msgstr ""
+
+#: waeup/custom/interfaces.py:78
+msgid "Maintenance Fee"
+msgstr ""
+
+#: waeup/custom/interfaces.py:84
+msgid "Gown Fee"
+msgstr ""
+
+#: waeup/custom/interfaces.py:90
+msgid "Transfer Fee"
+msgstr ""
+
+#: waeup/custom/interswitch/browser.py:115
+msgid "Request CollegePAY webservice"
+msgstr ""
+
+#: waeup/custom/interswitch/browser.py:127
+msgid "Submit data to CollegePAY (Interswitch Payment Gateway)"
+msgstr ""
+
+#: waeup/custom/interswitch/browser.py:128
+msgid "Submit"
+msgstr ""
+
+#: waeup/custom/interswitch/browser.py:137
+msgid "Payment ticket can't be re-send to CollegePAY."
+msgstr ""
+
+#. Default: ""
+#: waeup/custom/interswitch/browser.py:202
+#: waeup/custom/interswitch/browser.py:271
+msgid "Unsuccessful callback: ${a}"
+msgstr ""
+
+#: waeup/custom/interswitch/browser.py:203
+msgid "Incomplete query string."
+msgstr ""
+
+#: waeup/custom/interswitch/browser.py:87
+msgid "CollegePAY"
+msgstr ""
+
+#: waeup/custom/interswitch/browser.py:99
+msgid "Request CollegePAY callback"
+msgstr ""
+
+#: waeup/custom/interswitch/browser_templates/goto_interswitch.pt:4
+msgid ""
+"These data will be submitted to the CollegePAY payment gateway, please check:"
+msgstr ""
+
+#: waeup/custom/students/utils.py:70
+msgid "Session configuration object is not available."
+msgstr ""
+
+#: waeup/custom/students/utils.py:91
+msgid "Amount could not be determined."
+msgstr ""
+
+#. Default: ""
+#: waeup/custom/utils/utils.py:30 waeup/custom/utils/utils.py:38
+#: waeup/custom/utils/utils.py:46
+msgid "Valid callback received. ${a}"
+msgstr ""
+
+#: waeup/custom/utils/utils.py:50
+msgid "Valid callback received."
+msgstr ""
Index: /main/waeup.uniben/trunk/src/waeup/uniben/locales/fr/LC_MESSAGES/waeup.custom.po
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/locales/fr/LC_MESSAGES/waeup.custom.po	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/locales/fr/LC_MESSAGES/waeup.custom.po	(revision 8020)
@@ -0,0 +1,184 @@
+# #############################################################################
+#
+# Copyright (c) 2003-2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+# #############################################################################
+msgid ""
+msgstr ""
+"Project-Id-Version: WAeUP.Kofa\n"
+"POT-Creation-Date: Mon Apr  2 12:28:41 2012\n"
+"PO-Revision-Date: 2012-03-09 08:17+0100\n"
+"Last-Translator: Henrik Bettermann <henrik@waeup.org>\n"
+"Language-Team: WAeUP Niger <isouaba@waeup.org>\n"
+"Language: fr-FR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: zope/app/locales/extract.py\n"
+"X-Poedit-Language: French\n"
+"X-Poedit-Country: NIGER\n"
+"X-Poedit-SourceCharset: utf-8\n"
+
+#: waeup/custom/applicants/interfaces.py:36
+msgid "Employer"
+msgstr ""
+
+#: waeup/custom/browser/theming.py:30
+msgid "Custom Theme 1"
+msgstr ""
+
+#: waeup/custom/etranzact/browser.py:104
+#: waeup/custom/interswitch/browser.py:214
+#: waeup/custom/interswitch/browser.py:284
+msgid "Wrong amount"
+msgstr ""
+
+#: waeup/custom/etranzact/browser.py:112
+#: waeup/custom/interswitch/browser.py:292
+msgid "Wrong transaction id"
+msgstr ""
+
+#: waeup/custom/etranzact/browser.py:36
+msgid "Query eTranzact History"
+msgstr ""
+
+#: waeup/custom/etranzact/browser.py:47
+msgid "Submit to eTranzact"
+msgstr ""
+
+#: waeup/custom/etranzact/browser.py:48
+msgid "Requery eTranzact History"
+msgstr ""
+
+#: waeup/custom/etranzact/browser.py:60
+#: waeup/custom/interswitch/browser.py:189
+#: waeup/custom/interswitch/browser.py:253
+msgid "This ticket has already been paid."
+msgstr ""
+
+#: waeup/custom/etranzact/browser.py:72
+msgid "Unsuccessful callback"
+msgstr ""
+
+#. Default: ""
+#: waeup/custom/etranzact/browser.py:79 waeup/custom/etranzact/browser.py:87
+#: waeup/custom/interswitch/browser.py:260
+msgid "Invalid callback: ${a}"
+msgstr ""
+
+#: waeup/custom/etranzact/browser.py:93
+msgid "eTranzact IOError"
+msgstr ""
+
+#: waeup/custom/etranzact/browser_templates/enterpin.pt:6
+msgid "Confirmation Number (PIN):"
+msgstr ""
+
+#: waeup/custom/interfaces.py:105
+msgid "Academic Session"
+msgstr ""
+
+#: waeup/custom/interfaces.py:34
+msgid "School Fee (ignored)"
+msgstr ""
+
+#: waeup/custom/interfaces.py:40
+msgid "Surcharge Portal Provider"
+msgstr ""
+
+#: waeup/custom/interfaces.py:46
+msgid "Surcharge Interswitch"
+msgstr ""
+
+#: waeup/custom/interfaces.py:52
+msgid "Surcharge 3"
+msgstr ""
+
+#: waeup/custom/interfaces.py:58
+msgid "Clearance Fee"
+msgstr ""
+
+#: waeup/custom/interfaces.py:64
+msgid "Booking Fee"
+msgstr ""
+
+#: waeup/custom/interfaces.py:70
+msgid "Acceptance Fee"
+msgstr ""
+
+#: waeup/custom/interfaces.py:78
+msgid "Maintenance Fee"
+msgstr ""
+
+#: waeup/custom/interfaces.py:84
+msgid "Gown Fee"
+msgstr ""
+
+#: waeup/custom/interfaces.py:90
+msgid "Transfer Fee"
+msgstr ""
+
+#: waeup/custom/interswitch/browser.py:115
+msgid "Request CollegePAY webservice"
+msgstr ""
+
+#: waeup/custom/interswitch/browser.py:127
+msgid "Submit data to CollegePAY (Interswitch Payment Gateway)"
+msgstr ""
+
+#: waeup/custom/interswitch/browser.py:128
+msgid "Submit"
+msgstr ""
+
+#: waeup/custom/interswitch/browser.py:137
+msgid "Payment ticket can't be re-send to CollegePAY."
+msgstr ""
+
+#. Default: ""
+#: waeup/custom/interswitch/browser.py:202
+#: waeup/custom/interswitch/browser.py:271
+msgid "Unsuccessful callback: ${a}"
+msgstr ""
+
+#: waeup/custom/interswitch/browser.py:203
+msgid "Incomplete query string."
+msgstr ""
+
+#: waeup/custom/interswitch/browser.py:87
+msgid "CollegePAY"
+msgstr ""
+
+#: waeup/custom/interswitch/browser.py:99
+msgid "Request CollegePAY callback"
+msgstr ""
+
+#: waeup/custom/interswitch/browser_templates/goto_interswitch.pt:4
+msgid ""
+"These data will be submitted to the CollegePAY payment gateway, please check:"
+msgstr ""
+
+#: waeup/custom/students/utils.py:70
+msgid "Session configuration object is not available."
+msgstr ""
+
+#: waeup/custom/students/utils.py:91
+msgid "Amount could not be determined."
+msgstr ""
+
+#. Default: ""
+#: waeup/custom/utils/utils.py:30 waeup/custom/utils/utils.py:38
+#: waeup/custom/utils/utils.py:46
+msgid "Valid callback received. ${a}"
+msgstr ""
+
+#: waeup/custom/utils/utils.py:50
+msgid "Valid callback received."
+msgstr ""
Index: /main/waeup.uniben/trunk/src/waeup/uniben/locales/ha/LC_MESSAGES/waeup.custom.po
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/locales/ha/LC_MESSAGES/waeup.custom.po	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/locales/ha/LC_MESSAGES/waeup.custom.po	(revision 8020)
@@ -0,0 +1,184 @@
+# #############################################################################
+#
+# Copyright (c) 2003-2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+# #############################################################################
+msgid ""
+msgstr ""
+"Project-Id-Version: WAeUP.Kofa\n"
+"POT-Creation-Date: Mon Apr  2 12:28:41 2012\n"
+"PO-Revision-Date: 2012-03-09 08:16+0100\n"
+"Last-Translator: Henrik Bettermann <henrik@waeup.org>\n"
+"Language-Team: WAeUP Niger <isouaba@waeup.org>\n"
+"Language: ha-NG-Latn\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: zope/app/locales/extract.py\n"
+"X-Poedit-Language: Hausa\n"
+"X-Poedit-Country: NIGER\n"
+"X-Poedit-SourceCharset: utf-8\n"
+
+#: waeup/custom/applicants/interfaces.py:36
+msgid "Employer"
+msgstr ""
+
+#: waeup/custom/browser/theming.py:30
+msgid "Custom Theme 1"
+msgstr ""
+
+#: waeup/custom/etranzact/browser.py:104
+#: waeup/custom/interswitch/browser.py:214
+#: waeup/custom/interswitch/browser.py:284
+msgid "Wrong amount"
+msgstr ""
+
+#: waeup/custom/etranzact/browser.py:112
+#: waeup/custom/interswitch/browser.py:292
+msgid "Wrong transaction id"
+msgstr ""
+
+#: waeup/custom/etranzact/browser.py:36
+msgid "Query eTranzact History"
+msgstr ""
+
+#: waeup/custom/etranzact/browser.py:47
+msgid "Submit to eTranzact"
+msgstr ""
+
+#: waeup/custom/etranzact/browser.py:48
+msgid "Requery eTranzact History"
+msgstr ""
+
+#: waeup/custom/etranzact/browser.py:60
+#: waeup/custom/interswitch/browser.py:189
+#: waeup/custom/interswitch/browser.py:253
+msgid "This ticket has already been paid."
+msgstr ""
+
+#: waeup/custom/etranzact/browser.py:72
+msgid "Unsuccessful callback"
+msgstr ""
+
+#. Default: ""
+#: waeup/custom/etranzact/browser.py:79 waeup/custom/etranzact/browser.py:87
+#: waeup/custom/interswitch/browser.py:260
+msgid "Invalid callback: ${a}"
+msgstr ""
+
+#: waeup/custom/etranzact/browser.py:93
+msgid "eTranzact IOError"
+msgstr ""
+
+#: waeup/custom/etranzact/browser_templates/enterpin.pt:6
+msgid "Confirmation Number (PIN):"
+msgstr ""
+
+#: waeup/custom/interfaces.py:105
+msgid "Academic Session"
+msgstr ""
+
+#: waeup/custom/interfaces.py:34
+msgid "School Fee (ignored)"
+msgstr ""
+
+#: waeup/custom/interfaces.py:40
+msgid "Surcharge Portal Provider"
+msgstr ""
+
+#: waeup/custom/interfaces.py:46
+msgid "Surcharge Interswitch"
+msgstr ""
+
+#: waeup/custom/interfaces.py:52
+msgid "Surcharge 3"
+msgstr ""
+
+#: waeup/custom/interfaces.py:58
+msgid "Clearance Fee"
+msgstr ""
+
+#: waeup/custom/interfaces.py:64
+msgid "Booking Fee"
+msgstr ""
+
+#: waeup/custom/interfaces.py:70
+msgid "Acceptance Fee"
+msgstr ""
+
+#: waeup/custom/interfaces.py:78
+msgid "Maintenance Fee"
+msgstr ""
+
+#: waeup/custom/interfaces.py:84
+msgid "Gown Fee"
+msgstr ""
+
+#: waeup/custom/interfaces.py:90
+msgid "Transfer Fee"
+msgstr ""
+
+#: waeup/custom/interswitch/browser.py:115
+msgid "Request CollegePAY webservice"
+msgstr ""
+
+#: waeup/custom/interswitch/browser.py:127
+msgid "Submit data to CollegePAY (Interswitch Payment Gateway)"
+msgstr ""
+
+#: waeup/custom/interswitch/browser.py:128
+msgid "Submit"
+msgstr ""
+
+#: waeup/custom/interswitch/browser.py:137
+msgid "Payment ticket can't be re-send to CollegePAY."
+msgstr ""
+
+#. Default: ""
+#: waeup/custom/interswitch/browser.py:202
+#: waeup/custom/interswitch/browser.py:271
+msgid "Unsuccessful callback: ${a}"
+msgstr ""
+
+#: waeup/custom/interswitch/browser.py:203
+msgid "Incomplete query string."
+msgstr ""
+
+#: waeup/custom/interswitch/browser.py:87
+msgid "CollegePAY"
+msgstr ""
+
+#: waeup/custom/interswitch/browser.py:99
+msgid "Request CollegePAY callback"
+msgstr ""
+
+#: waeup/custom/interswitch/browser_templates/goto_interswitch.pt:4
+msgid ""
+"These data will be submitted to the CollegePAY payment gateway, please check:"
+msgstr ""
+
+#: waeup/custom/students/utils.py:70
+msgid "Session configuration object is not available."
+msgstr ""
+
+#: waeup/custom/students/utils.py:91
+msgid "Amount could not be determined."
+msgstr ""
+
+#. Default: ""
+#: waeup/custom/utils/utils.py:30 waeup/custom/utils/utils.py:38
+#: waeup/custom/utils/utils.py:46
+msgid "Valid callback received. ${a}"
+msgstr ""
+
+#: waeup/custom/utils/utils.py:50
+msgid "Valid callback received."
+msgstr ""
Index: /main/waeup.uniben/trunk/src/waeup/uniben/locales/waeup.custom.pot
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/locales/waeup.custom.pot	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/locales/waeup.custom.pot	(revision 8020)
@@ -0,0 +1,182 @@
+##############################################################################
+#
+# Copyright (c) 2003-2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+msgid ""
+msgstr ""
+"Project-Id-Version: Development/Unknown\n"
+"POT-Creation-Date: Mon Apr  2 12:28:41 2012\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: Zope 3 Developers <zope3-dev@zope.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: zope/app/locales/extract.py\n"
+
+#: waeup/custom/applicants/interfaces.py:36
+msgid "Employer"
+msgstr ""
+
+#: waeup/custom/browser/theming.py:30
+msgid "Custom Theme 1"
+msgstr ""
+
+#: waeup/custom/etranzact/browser.py:104
+#: waeup/custom/interswitch/browser.py:214
+#: waeup/custom/interswitch/browser.py:284
+msgid "Wrong amount"
+msgstr ""
+
+#: waeup/custom/etranzact/browser.py:112
+#: waeup/custom/interswitch/browser.py:292
+msgid "Wrong transaction id"
+msgstr ""
+
+#: waeup/custom/etranzact/browser.py:36
+msgid "Query eTranzact History"
+msgstr ""
+
+#: waeup/custom/etranzact/browser.py:47
+msgid "Submit to eTranzact"
+msgstr ""
+
+#: waeup/custom/etranzact/browser.py:48
+msgid "Requery eTranzact History"
+msgstr ""
+
+#: waeup/custom/etranzact/browser.py:60
+#: waeup/custom/interswitch/browser.py:189
+#: waeup/custom/interswitch/browser.py:253
+msgid "This ticket has already been paid."
+msgstr ""
+
+#: waeup/custom/etranzact/browser.py:72
+msgid "Unsuccessful callback"
+msgstr ""
+
+#: waeup/custom/etranzact/browser.py:79
+#: waeup/custom/etranzact/browser.py:87
+#: waeup/custom/interswitch/browser.py:260
+#. Default: ""
+msgid "Invalid callback: ${a}"
+msgstr ""
+
+#: waeup/custom/etranzact/browser.py:93
+msgid "eTranzact IOError"
+msgstr ""
+
+#: waeup/custom/etranzact/browser_templates/enterpin.pt:6
+msgid "Confirmation Number (PIN):"
+msgstr ""
+
+#: waeup/custom/interfaces.py:105
+msgid "Academic Session"
+msgstr ""
+
+#: waeup/custom/interfaces.py:34
+msgid "School Fee (ignored)"
+msgstr ""
+
+#: waeup/custom/interfaces.py:40
+msgid "Surcharge Portal Provider"
+msgstr ""
+
+#: waeup/custom/interfaces.py:46
+msgid "Surcharge Interswitch"
+msgstr ""
+
+#: waeup/custom/interfaces.py:52
+msgid "Surcharge 3"
+msgstr ""
+
+#: waeup/custom/interfaces.py:58
+msgid "Clearance Fee"
+msgstr ""
+
+#: waeup/custom/interfaces.py:64
+msgid "Booking Fee"
+msgstr ""
+
+#: waeup/custom/interfaces.py:70
+msgid "Acceptance Fee"
+msgstr ""
+
+#: waeup/custom/interfaces.py:78
+msgid "Maintenance Fee"
+msgstr ""
+
+#: waeup/custom/interfaces.py:84
+msgid "Gown Fee"
+msgstr ""
+
+#: waeup/custom/interfaces.py:90
+msgid "Transfer Fee"
+msgstr ""
+
+#: waeup/custom/interswitch/browser.py:115
+msgid "Request CollegePAY webservice"
+msgstr ""
+
+#: waeup/custom/interswitch/browser.py:127
+msgid "Submit data to CollegePAY (Interswitch Payment Gateway)"
+msgstr ""
+
+#: waeup/custom/interswitch/browser.py:128
+msgid "Submit"
+msgstr ""
+
+#: waeup/custom/interswitch/browser.py:137
+msgid "Payment ticket can't be re-send to CollegePAY."
+msgstr ""
+
+#: waeup/custom/interswitch/browser.py:202
+#: waeup/custom/interswitch/browser.py:271
+#. Default: ""
+msgid "Unsuccessful callback: ${a}"
+msgstr ""
+
+#: waeup/custom/interswitch/browser.py:203
+msgid "Incomplete query string."
+msgstr ""
+
+#: waeup/custom/interswitch/browser.py:87
+msgid "CollegePAY"
+msgstr ""
+
+#: waeup/custom/interswitch/browser.py:99
+msgid "Request CollegePAY callback"
+msgstr ""
+
+#: waeup/custom/interswitch/browser_templates/goto_interswitch.pt:4
+msgid "These data will be submitted to the CollegePAY payment gateway, please check:"
+msgstr ""
+
+#: waeup/custom/students/utils.py:70
+msgid "Session configuration object is not available."
+msgstr ""
+
+#: waeup/custom/students/utils.py:91
+msgid "Amount could not be determined."
+msgstr ""
+
+#: waeup/custom/utils/utils.py:30
+#: waeup/custom/utils/utils.py:38
+#: waeup/custom/utils/utils.py:46
+#. Default: ""
+msgid "Valid callback received. ${a}"
+msgstr ""
+
+#: waeup/custom/utils/utils.py:50
+msgid "Valid callback received."
+msgstr ""
+
Index: /main/waeup.uniben/trunk/src/waeup/uniben/locales/yo/LC_MESSAGES/waeup.custom.po
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/locales/yo/LC_MESSAGES/waeup.custom.po	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/locales/yo/LC_MESSAGES/waeup.custom.po	(revision 8020)
@@ -0,0 +1,184 @@
+# #############################################################################
+#
+# Copyright (c) 2003-2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+# #############################################################################
+msgid ""
+msgstr ""
+"Project-Id-Version: WAeUP.Kofa\n"
+"POT-Creation-Date: Mon Apr  2 12:28:41 2012\n"
+"PO-Revision-Date: 2012-03-09 08:17+0100\n"
+"Last-Translator: Henrik Bettermann <henrik@waeup.org>\n"
+"Language-Team: WAeUP Nigeria <isouaba@waeup.org>\n"
+"Language: yo-NG\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Generated-By: zope/app/locales/extract.py\n"
+"X-Poedit-Language: Yoruba\n"
+"X-Poedit-Country: NIGERIA\n"
+"X-Poedit-SourceCharset: utf-8\n"
+
+#: waeup/custom/applicants/interfaces.py:36
+msgid "Employer"
+msgstr ""
+
+#: waeup/custom/browser/theming.py:30
+msgid "Custom Theme 1"
+msgstr ""
+
+#: waeup/custom/etranzact/browser.py:104
+#: waeup/custom/interswitch/browser.py:214
+#: waeup/custom/interswitch/browser.py:284
+msgid "Wrong amount"
+msgstr ""
+
+#: waeup/custom/etranzact/browser.py:112
+#: waeup/custom/interswitch/browser.py:292
+msgid "Wrong transaction id"
+msgstr ""
+
+#: waeup/custom/etranzact/browser.py:36
+msgid "Query eTranzact History"
+msgstr ""
+
+#: waeup/custom/etranzact/browser.py:47
+msgid "Submit to eTranzact"
+msgstr ""
+
+#: waeup/custom/etranzact/browser.py:48
+msgid "Requery eTranzact History"
+msgstr ""
+
+#: waeup/custom/etranzact/browser.py:60
+#: waeup/custom/interswitch/browser.py:189
+#: waeup/custom/interswitch/browser.py:253
+msgid "This ticket has already been paid."
+msgstr ""
+
+#: waeup/custom/etranzact/browser.py:72
+msgid "Unsuccessful callback"
+msgstr ""
+
+#. Default: ""
+#: waeup/custom/etranzact/browser.py:79 waeup/custom/etranzact/browser.py:87
+#: waeup/custom/interswitch/browser.py:260
+msgid "Invalid callback: ${a}"
+msgstr ""
+
+#: waeup/custom/etranzact/browser.py:93
+msgid "eTranzact IOError"
+msgstr ""
+
+#: waeup/custom/etranzact/browser_templates/enterpin.pt:6
+msgid "Confirmation Number (PIN):"
+msgstr ""
+
+#: waeup/custom/interfaces.py:105
+msgid "Academic Session"
+msgstr ""
+
+#: waeup/custom/interfaces.py:34
+msgid "School Fee (ignored)"
+msgstr ""
+
+#: waeup/custom/interfaces.py:40
+msgid "Surcharge Portal Provider"
+msgstr ""
+
+#: waeup/custom/interfaces.py:46
+msgid "Surcharge Interswitch"
+msgstr ""
+
+#: waeup/custom/interfaces.py:52
+msgid "Surcharge 3"
+msgstr ""
+
+#: waeup/custom/interfaces.py:58
+msgid "Clearance Fee"
+msgstr ""
+
+#: waeup/custom/interfaces.py:64
+msgid "Booking Fee"
+msgstr ""
+
+#: waeup/custom/interfaces.py:70
+msgid "Acceptance Fee"
+msgstr ""
+
+#: waeup/custom/interfaces.py:78
+msgid "Maintenance Fee"
+msgstr ""
+
+#: waeup/custom/interfaces.py:84
+msgid "Gown Fee"
+msgstr ""
+
+#: waeup/custom/interfaces.py:90
+msgid "Transfer Fee"
+msgstr ""
+
+#: waeup/custom/interswitch/browser.py:115
+msgid "Request CollegePAY webservice"
+msgstr ""
+
+#: waeup/custom/interswitch/browser.py:127
+msgid "Submit data to CollegePAY (Interswitch Payment Gateway)"
+msgstr ""
+
+#: waeup/custom/interswitch/browser.py:128
+msgid "Submit"
+msgstr ""
+
+#: waeup/custom/interswitch/browser.py:137
+msgid "Payment ticket can't be re-send to CollegePAY."
+msgstr ""
+
+#. Default: ""
+#: waeup/custom/interswitch/browser.py:202
+#: waeup/custom/interswitch/browser.py:271
+msgid "Unsuccessful callback: ${a}"
+msgstr ""
+
+#: waeup/custom/interswitch/browser.py:203
+msgid "Incomplete query string."
+msgstr ""
+
+#: waeup/custom/interswitch/browser.py:87
+msgid "CollegePAY"
+msgstr ""
+
+#: waeup/custom/interswitch/browser.py:99
+msgid "Request CollegePAY callback"
+msgstr ""
+
+#: waeup/custom/interswitch/browser_templates/goto_interswitch.pt:4
+msgid ""
+"These data will be submitted to the CollegePAY payment gateway, please check:"
+msgstr ""
+
+#: waeup/custom/students/utils.py:70
+msgid "Session configuration object is not available."
+msgstr ""
+
+#: waeup/custom/students/utils.py:91
+msgid "Amount could not be determined."
+msgstr ""
+
+#. Default: ""
+#: waeup/custom/utils/utils.py:30 waeup/custom/utils/utils.py:38
+#: waeup/custom/utils/utils.py:46
+msgid "Valid callback received. ${a}"
+msgstr ""
+
+#: waeup/custom/utils/utils.py:50
+msgid "Valid callback received."
+msgstr ""
Index: /main/waeup.uniben/trunk/src/waeup/uniben/mail.zcml
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/mail.zcml	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/mail.zcml	(revision 8020)
@@ -0,0 +1,82 @@
+<configure xmlns="http://namespaces.zope.org/zope"
+           xmlns:mail="http://namespaces.zope.org/mail">
+  <!--
+
+   SMTP Settings
+   *************
+
+   In here we define the mailers and mail deliveries we want to be
+   used by the portal.
+
+   They are set here (and not dynamically in the portal) to increase
+   security. Even portal users with all permissions cannot change mail
+   server credentials and related. You need filesystem access to
+   change/override the settings in here.
+
+   In the portal you can pick one of the deliveries for use with all
+   SMTP (aka mail) related functionality.
+
+   Each delivery (direct or queued) is bound to a certain mailer,
+   defined by a 'mail:smtpMailer' directive.
+
+   The smtpMailer directives define real mail hosts (local or remote)
+   where mail from the portal should be processed.
+
+   The 'mail:directDelivery' and 'mail:queuedDelivery' directives send
+   local mail to the mailer set in their 'mailer' attribute. While
+   directDelivery sends mail immediately when a request is finished
+   (see zope.sendmail for more info), queuedDelivery handles mail in a
+   background thread and sends mail independently from requests.
+
+   By default in Kofa we have a fake mailer activated, that does not
+   send real mail to the outside world.
+  -->
+
+  <!-- A mail server on localhost -->
+  <!-- Mail server definitions require the following attributes:
+
+        - name: (string) name to be used by deliveries
+
+       Mail server definitions allow the following additional attribs:
+
+        - hostname   (string) hostname or IP(?) of mail server to use
+                              ('localhost' by default)
+        - port       (int number) port to connect to SMTP mailer
+                              (25 by default)
+        - username   (string) Username to log in to mail server
+        - password   (string) Password to log in to mail server
+
+  -->
+  <mail:smtpMailer
+      name="smtp.mailserver"
+      hostname="localhost"
+      port="25"
+      />
+
+  <!-- The mail deliveries -->
+  <!-- Mail deliveries require the following attributes:
+
+        - name:       (string) this string will appear in portal select
+                               boxes
+        - permission: (string) zope default perms only
+        - mailer:     (string) name of some mailer registered above or
+                               'smtp'
+        - queuePath:  (string) must exist and contain cur/, new/, and
+                               tmp/ dirs. Relative paths are relative
+                               to the directory this files lives in.
+
+   -->
+  <mail:queuedDelivery
+      name="Queued SMTP on mail server"
+      permission="zope.Public"
+      mailer="smtp.mailserver"
+      queuePath="../../../var/mailqueue"
+      />
+
+  <mail:directDelivery
+      name="Direct SMTP on mail server"
+      permission="zope.Public"
+      mailer="smtp.mailserver"
+      />
+
+</configure>
Index: /main/waeup.uniben/trunk/src/waeup/uniben/meta.zcml
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/meta.zcml	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/meta.zcml	(revision 8020)
@@ -0,0 +1,12 @@
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    xmlns:meta="http://namespaces.zope.org/meta"
+    xmlns:grok="http://namespaces.zope.org/grok">
+
+  <include package="grok" file="meta.zcml" />
+  <!-- include package="z3c.autoinclude" file="meta.zcml" / -->
+
+  <!-- Load the grokkers -->
+  <grok:grok package=".meta" />
+
+</configure>
Index: /main/waeup.uniben/trunk/src/waeup/uniben/overrides.zcml
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/overrides.zcml	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/overrides.zcml	(revision 8020)
@@ -0,0 +1,5 @@
+<configure xmlns="http://namespaces.zope.org/zope"
+           xmlns:grok="http://namespaces.zope.org/grok">
+  <includeDependencies package="." />
+  <grok:grok package="." />
+</configure>
Index: /main/waeup.uniben/trunk/src/waeup/uniben/students/__init__.py
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/students/__init__.py	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/students/__init__.py	(revision 8020)
@@ -0,0 +1,1 @@
+# Make this a package.
Index: /main/waeup.uniben/trunk/src/waeup/uniben/students/batching.py
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/students/batching.py	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/students/batching.py	(revision 8020)
@@ -0,0 +1,34 @@
+## $Id$
+##
+## Copyright (C) 2012 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+"""Batch processing components for student objects.
+
+Batch processors eat CSV files to add, update or remove large numbers
+of certain kinds of objects at once.
+
+Here we define the processors for students specific objects like
+students, studycourses, payment tickets and accommodation tickets.
+"""
+from waeup.uniben.students.interfaces import (
+    IStudent, 
+    )
+from waeup.kofa.students.batching import StudentProcessor
+
+class StudentProcessor(StudentProcessor):
+    """A batch processor for IStudent objects.
+    """
+    iface = IStudent
Index: /main/waeup.uniben/trunk/src/waeup/uniben/students/browser.py
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/students/browser.py	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/students/browser.py	(revision 8020)
@@ -0,0 +1,120 @@
+## $Id$
+##
+## Copyright (C) 2012 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+import grok
+from zope.formlib.textwidgets import BytesDisplayWidget
+from waeup.kofa.widgets.datewidget import (
+    FriendlyDateWidget, FriendlyDateDisplayWidget
+    )
+from waeup.kofa.students.browser import (
+    StudentPersonalDisplayFormPage, StudentPersonalManageFormPage,
+    StudentClearanceManageFormPage, StudentClearanceEditFormPage,
+    StudentClearanceDisplayFormPage, OnlinePaymentCallbackPage,
+    ExportPDFClearanceSlipPage)
+from waeup.kofa.students.viewlets import RequestCallbackActionButton
+from waeup.uniben.students.interfaces import (
+    IStudent, IStudentPersonal,
+    IUGStudentClearance,IPGStudentClearance,
+    )
+from waeup.uniben.interfaces import MessageFactory as _
+
+class RequestCallbackActionButton(RequestCallbackActionButton):
+    """ Do not display the base package callback button in custom pages.
+    """
+    @property
+    def target_url(self):
+        return ''
+
+class OnlinePaymentCallbackPage(OnlinePaymentCallbackPage):
+    """ Neutralize callback simulation view
+    """
+    def update(self):
+        return
+
+class StudentPersonalDisplayFormPage(StudentPersonalDisplayFormPage):
+    """ Page to display student personal data
+    """
+    grok.context(IStudent)
+    form_fields = grok.AutoFields(IStudentPersonal)
+    form_fields['perm_address'].custom_widget = BytesDisplayWidget
+
+class StudentPersonalManageFormPage(StudentPersonalManageFormPage):
+    """ Page to edit student clearance data
+    """
+    grok.context(IStudent)
+    form_fields = grok.AutoFields(IStudentPersonal)
+
+class StudentClearanceDisplayFormPage(StudentClearanceDisplayFormPage):
+    """ Page to display student clearance data
+    """
+    grok.context(IStudent)
+
+    @property
+    def form_fields(self):
+        cm = getattr(self.context,'current_mode', None)
+        if cm is not None and cm.startswith('pg'):
+            form_fields = grok.AutoFields(IPGStudentClearance).omit('clearance_locked')
+        else:
+            form_fields = grok.AutoFields(IUGStudentClearance).omit('clearance_locked')
+        form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
+        return form_fields
+
+class ExportPDFClearanceSlipPage(ExportPDFClearanceSlipPage):
+    """Deliver a PDF slip of the context.
+    """
+    grok.context(IStudent)
+
+    @property
+    def form_fields(self):
+        cm = getattr(self.context,'current_mode', None)
+        if cm is not None and cm.startswith('pg'):
+            form_fields = grok.AutoFields(IPGStudentClearance).omit('clearance_locked')
+        else:
+            form_fields = grok.AutoFields(IUGStudentClearance).omit('clearance_locked')
+        form_fields['date_of_birth'].custom_widget = FriendlyDateDisplayWidget('le')
+        return form_fields
+
+class StudentClearanceManageFormPage(StudentClearanceManageFormPage):
+    """ Page to edit student clearance data
+    """
+    grok.context(IStudent)
+
+    @property
+    def form_fields(self):
+        cm = getattr(self.context,'current_mode', None)
+        if cm is not None and cm.startswith('pg'):
+            form_fields = grok.AutoFields(IPGStudentClearance)
+        else:
+            form_fields = grok.AutoFields(IUGStudentClearance)
+        form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
+        return form_fields
+
+class StudentClearanceEditFormPage(StudentClearanceEditFormPage):
+    """ View to edit student clearance data by student
+    """
+    grok.context(IStudent)
+
+    @property
+    def form_fields(self):
+        cm = getattr(self.context,'current_mode', None)
+        if cm is not None and cm.startswith('pg'):
+            form_fields = grok.AutoFields(IPGStudentClearance).omit('clearance_locked')
+        else:
+            form_fields = grok.AutoFields(IUGStudentClearance).omit('clearance_locked')
+        form_fields['date_of_birth'].custom_widget = FriendlyDateWidget('le-year')
+        return form_fields
+
Index: /main/waeup.uniben/trunk/src/waeup/uniben/students/interfaces.py
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/students/interfaces.py	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/students/interfaces.py	(revision 8020)
@@ -0,0 +1,84 @@
+## $Id$
+##
+## Copyright (C) 2012 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+from zope import schema
+from waeup.kofa.schema import TextLineChoice
+from waeup.kofa.interfaces import SimpleKofaVocabulary
+from waeup.kofa.students.interfaces import (
+    IStudentBase,IUGStudentClearance,IPGStudentClearance,
+    IStudentPersonal,IStudentNavigation,
+    )
+from waeup.kofa.students.vocabularies import (
+    lgas_vocab, nats_vocab, contextual_reg_num_source)
+
+class IStudentBase(IStudentBase):
+    """Representation of student base data.
+
+    """
+
+    reg_number = TextLineChoice(
+        title = u'Registration Number',
+        required = False,
+        readonly = False,
+        source = contextual_reg_num_source,
+        )
+
+class IStudentPersonal(IStudentPersonal):
+    """Student personal data.
+
+    """
+
+    marit_stat = schema.Choice(
+        title = u'Maritual Status',
+        default = 'unmarried',
+        required = False,
+        vocabulary = SimpleKofaVocabulary(
+            ('Unmarried', 'unmarried'),
+            ('Married', 'married'),)
+        )
+
+class IUGStudentClearance(IUGStudentClearance):
+    """Representation of ug student clearance data.
+
+    """
+    date_of_birth = schema.Date(
+        title = u'Date of Birth',
+        required = False,
+        )
+
+    nationality = schema.Choice(
+        source = nats_vocab,
+        title = u'Nationality',
+        default = 'nigeria',
+        required = False,
+        )
+
+    lga = schema.Choice(
+        source = lgas_vocab,
+        title = u'State/LGA',
+        required = False,
+        )
+
+class IPGStudentClearance(IUGStudentClearance):
+    """Representation of pg student clearance data.
+
+    """
+
+class IStudent(IStudentBase,IUGStudentClearance,IPGStudentClearance,IStudentPersonal):
+    """Representation of a student.
+
+    """
Index: /main/waeup.uniben/trunk/src/waeup/uniben/students/student.py
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/students/student.py	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/students/student.py	(revision 8020)
@@ -0,0 +1,50 @@
+## $Id$
+##
+## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+"""
+Container for the various objects owned by students.
+"""
+import grok
+from zope.interface import implementedBy
+from waeup.kofa.utils.helpers import attrs_to_fields
+from waeup.kofa.students.student import Student, StudentFactory
+from waeup.uniben.students.interfaces import IStudent, IStudentNavigation
+
+
+class Student(Student):
+    """This is a student container for the various objects
+    owned by students.
+    """
+    grok.implements(IStudent, IStudentNavigation)
+    grok.provides(IStudent)
+
+
+# Set all attributes of Student required in IStudent as field
+# properties. Doing this, we do not have to set initial attributes
+# ourselves and as a bonus we get free validation when an attribute is
+# set.
+Student = attrs_to_fields(Student)
+
+class StudentFactory(StudentFactory):
+    """A factory for students.
+    """
+
+    def __call__(self, *args, **kw):
+        return Student()
+
+    def getInterfaces(self):
+        return implementedBy(Student)
Index: /main/waeup.uniben/trunk/src/waeup/uniben/students/tests/__init__.py
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/students/tests/__init__.py	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/students/tests/__init__.py	(revision 8020)
@@ -0,0 +1,1 @@
+# Make this a package.
Index: /main/waeup.uniben/trunk/src/waeup/uniben/students/tests/sample_student_data.csv
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/students/tests/sample_student_data.csv	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/students/tests/sample_student_data.csv	(revision 8020)
@@ -0,0 +1,4 @@
+firstname,lastname,reg_number,date_of_birth,matric_number,email,phone,sex
+Aaren,Pieri,1,,100000,aa@aa.ng,1234,m
+Aaren,Finau,2,,100001,aa@aa.ng,1234,m
+Aaren,Berson,3,,100002,aa@aa.ng,1234,m
Index: /main/waeup.uniben/trunk/src/waeup/uniben/students/tests/test_browser.py
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/students/tests/test_browser.py	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/students/tests/test_browser.py	(revision 8020)
@@ -0,0 +1,168 @@
+## $Id$
+##
+## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+import os
+import shutil
+import tempfile
+from hurry.workflow.interfaces import IWorkflowState
+from zope.component.hooks import setSite, clearSite
+from waeup.kofa.app import University
+from waeup.kofa.students.tests.test_browser import StudentsFullSetup
+from waeup.kofa.testing import FunctionalTestCase
+from waeup.kofa.students.batching import StudentProcessor as StudentProcessorBase
+from waeup.uniben.students.batching import StudentProcessor
+from waeup.uniben.testing import FunctionalLayer
+
+STUDENT_SAMPLE_DATA = open(
+    os.path.join(os.path.dirname(__file__), 'sample_student_data.csv'),
+    'rb').read()
+
+STUDENT_HEADER_FIELDS = STUDENT_SAMPLE_DATA.split(
+    '\n')[0].split(',')
+
+class StudentProcessorTest(FunctionalTestCase):
+    """Perform some batching tests.
+    """
+
+    layer = FunctionalLayer
+
+    def setUp(self):
+        super(StudentProcessorTest, self).setUp()
+        # Setup a sample site for each test
+        app = University()
+        self.dc_root = tempfile.mkdtemp()
+        app['datacenter'].setStoragePath(self.dc_root)
+
+        # Prepopulate the ZODB...
+        self.getRootFolder()['app'] = app
+        # we add the site immediately after creation to the
+        # ZODB. Catalogs and other local utilities are not setup
+        # before that step.
+        self.app = self.getRootFolder()['app']
+        # Set site here. Some of the following setup code might need
+        # to access grok.getSite() and should get our new app then
+        setSite(app)
+
+        self.processor_base = StudentProcessorBase()
+        self.processor = StudentProcessor()
+        self.workdir = tempfile.mkdtemp()
+        self.csv_file = os.path.join(self.workdir, 'sample_student_data.csv')
+        open(self.csv_file, 'wb').write(STUDENT_SAMPLE_DATA)
+
+    def tearDown(self):
+        super(StudentProcessorTest, self).tearDown()
+        shutil.rmtree(self.workdir)
+        shutil.rmtree(self.dc_root)
+        clearSite()
+        return
+
+    def test_import(self):
+        # We have an empty column 'date_of_birth' in  the import file.
+        # The original processor will fail because 'date_of_birth' is required
+        # in the base package.
+        num, num_warns, fin_file, fail_file = self.processor_base.doImport(
+            self.csv_file, STUDENT_HEADER_FIELDS)
+        self.assertEqual(num_warns,3)
+        assert len(self.app['students'].keys()) == 0
+        # The customized processor does not complain since 'date_of_birth' is
+        # not required in the custom package.
+        num, num_warns, fin_file, fail_file = self.processor.doImport(
+            self.csv_file, STUDENT_HEADER_FIELDS)
+        #print open(fail_file).read()
+        self.assertEqual(num_warns,0)
+        assert len(self.app['students'].keys()) == 3
+        shutil.rmtree(os.path.dirname(fin_file))
+
+
+class StudentUITests(StudentsFullSetup):
+    """Tests for customized student class views and pages
+    """
+
+    layer = FunctionalLayer
+
+    def test_manage_payments(self):
+        # Add missing configuration data
+        self.app['configuration']['2004'].gown_fee = 150.0
+        self.app['configuration']['2004'].transfer_fee = 90.0
+        self.app['configuration']['2004'].clearance_fee = 120.0
+        self.app['configuration']['2004'].maint_fee = 180.0
+
+        # Managers can add online payment tickets
+        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
+        self.browser.open(self.payments_path)
+        self.browser.getControl("Add online payment ticket").click()
+        self.browser.getControl("Create ticket").click()
+        self.assertMatches('...Amount could not be determined...',
+                           self.browser.contents)
+        IWorkflowState(self.student).setState('cleared')
+        self.browser.open(self.payments_path + '/addop')
+        self.browser.getControl("Create ticket").click()
+        self.assertMatches('...ticket created...',
+                           self.browser.contents)
+        ctrl = self.browser.getControl(name='val_id')
+        value = ctrl.options[0]
+        self.browser.getLink(value).click()
+        self.assertMatches('...Amount Authorized...',
+                           self.browser.contents)
+        # Set ticket paid
+        ticket = self.student['payments'].items()[0][1]
+        ticket.p_state = 'paid'
+        IWorkflowState(self.student).setState('returning')
+        self.browser.open(self.payments_path + '/addop')
+        self.browser.getControl("Create ticket").click()
+        self.assertMatches('...This type of payment has already been made...',
+                           self.browser.contents)
+        # Remove all payments so that we can add a school fee payment again
+        keys = [i for i in self.student['payments'].keys()]
+        for payment in keys:
+            del self.student['payments'][payment]
+        self.browser.open(self.payments_path + '/addop')
+        self.browser.getControl("Create ticket").click()
+        self.assertMatches('...ticket created...',
+                           self.browser.contents)
+        self.browser.open(self.payments_path + '/addop')
+        self.browser.getControl(name="form.p_category").value = ['gown']
+        self.browser.getControl("Create ticket").click()
+        self.browser.open(self.payments_path + '/addop')
+        self.browser.getControl(name="form.p_category").value = ['transfer']
+        self.browser.getControl("Create ticket").click()
+        self.browser.open(self.payments_path + '/addop')
+        self.browser.getControl(
+            name="form.p_category").value = ['bed_allocation']
+        self.browser.getControl("Create ticket").click()
+        self.browser.open(self.payments_path + '/addop')
+        self.browser.getControl(
+            name="form.p_category").value = ['hostel_maintenance']
+        self.browser.getControl("Create ticket").click()
+        self.browser.open(self.payments_path + '/addop')
+        self.browser.getControl(name="form.p_category").value = ['clearance']
+        self.browser.getControl("Create ticket").click()
+        self.certificate.study_mode = 'ug_pt'
+        self.browser.open(self.payments_path + '/addop')
+        self.browser.getControl(name="form.p_category").value = ['schoolfee']
+        self.browser.getControl("Create ticket").click()
+        self.assertMatches('...Amount could not be determined...',
+                           self.browser.contents)
+
+        # If the session configuration doesn't exist and error message will
+        # be shown
+        del self.app['configuration']['2004']
+        self.browser.open(self.payments_path)
+        self.browser.getControl("Add online payment ticket").click()
+        self.browser.getControl("Create ticket").click()
+        self.assertMatches('...Session configuration object is not...',
+                           self.browser.contents)
Index: /main/waeup.uniben/trunk/src/waeup/uniben/students/utils.py
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/students/utils.py	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/students/utils.py	(revision 8020)
@@ -0,0 +1,122 @@
+## $Id$
+##
+## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+import grok
+from waeup.kofa.students.workflow import CLEARED, RETURNING
+from waeup.kofa.students.utils import StudentsUtils
+from waeup.kofa.students.interfaces import IStudentsUtils
+from waeup.uniben.interfaces import MessageFactory as _
+
+def get_school_fee(student, surcharge):
+    study_mode = student['studycourse'].certificate.study_mode
+    entry_mode = student['studycourse'].entry_mode
+    state = student.state
+    #lga = student.lga
+    lga = 'nothing'
+    current_level = student['studycourse'].current_level
+
+    if study_mode.endswith('_ft'):
+        # fresh
+        if state == CLEARED:
+            return 40000.0 - surcharge
+        # returning
+        elif state == RETURNING:
+            return 20000.0 - surcharge
+        else:
+            return 0.0
+    else:
+        return 0.0
+
+class StudentsUtils(StudentsUtils):
+    """A collection of customized methods.
+
+    """
+    grok.implements(IStudentsUtils)
+
+    # not yet changed
+    def setReturningData(self, student):
+        student['studycourse'].current_level += 100
+        student['studycourse'].current_session += 1
+        verdict = student['studycourse'].current_verdict
+        student['studycourse'].current_verdict = '0'
+        student['studycourse'].previous_verdict = verdict
+        return
+
+    def getPaymentDetails(self, category, student):
+        d = {}
+        d['surcharge_1'] = d['surcharge_2'] = d['surcharge_3'] = 0.0
+        d['p_item'] = u''
+        d['amount'] = 0.0
+        d['error'] = u''
+        d['p_session'] = student['studycourse'].current_session
+        session = str(d['p_session'])
+        try:
+            academic_session = grok.getSite()['configuration'][session]
+        except KeyError:
+            d['error'] = _(u'Session configuration object is not available.')
+            return d
+        if category == 'transfer':
+            d['amount'] = academic_session.transfer_fee
+        elif category == 'gown':
+            d['amount'] = academic_session.gown_fee
+        elif category == 'bed_allocation':
+            d['amount'] = academic_session.booking_fee
+        elif category == 'hostel_maintenance':
+            d['amount'] = academic_session.maint_fee
+        elif category == 'clearance':
+            d['p_item'] = student['studycourse'].certificate.code
+            d['amount'] = academic_session.clearance_fee
+        elif category == 'schoolfee':
+            d['surcharge_1'] = academic_session.surcharge_1
+            d['surcharge_2'] = academic_session.surcharge_2
+            d['amount'] = get_school_fee(student, d['surcharge_1'] + d['surcharge_2'])
+            code = student['studycourse'].certificate.code
+            d['p_item'] = code
+            d['p_session'] += 1
+        if d['amount'] == 0.0:
+            d['error'] = _(u'Amount could not be determined.')
+        return d
+
+    VERDICTS_DICT = {
+        '0': 'not yet',
+        'A': 'Successful student',
+        'B': 'Student with carryover courses',
+        'C': 'Student on probation',
+        'D': 'Withdrawn from the faculty',
+        'E': 'Student who were previously on probation',
+        'F': 'Medical case',
+        'G': 'Absent from examination',
+        'H': 'Withheld results',
+        'I': 'Expelled/rusticated/suspended student',
+        'J': 'Temporary withdrawn from the university',
+        'K': 'Unregistered student',
+        'L': 'Referred student',
+        'M': 'Reinstatement',
+        'N': 'Student on transfer',
+        'O': 'NCE-III repeater',
+        'Y': 'No previous verdict',
+        'X': 'New 300 level student',
+        'Z': 'Successful student (provisional)',
+        'A1': 'First Class',
+        'A2': 'Second Class Upper',
+        'A3': 'Second Class Lower',
+        'A4': 'Third Class',
+        'A5': 'Pass',
+        'A6': 'Distinction',
+        'A7': 'Credit',
+        'A8': 'Merit',
+        }
Index: /main/waeup.uniben/trunk/src/waeup/uniben/testing.py
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/testing.py	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/testing.py	(revision 8020)
@@ -0,0 +1,27 @@
+## $Id$
+##
+## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+"""Testing support for :mod:`waeup.uniben`.
+"""
+import os.path
+import waeup.uniben
+from zope.app.testing.functional import ZCMLLayer
+
+ftesting_zcml = os.path.join(
+    os.path.dirname(waeup.uniben.__file__), 'ftesting.zcml')
+FunctionalLayer = ZCMLLayer(ftesting_zcml, __name__, 'FunctionalLayer',
+                            allow_teardown=True)
Index: /main/waeup.uniben/trunk/src/waeup/uniben/tests/__init__.py
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/tests/__init__.py	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/tests/__init__.py	(revision 8020)
@@ -0,0 +1,2 @@
+# This is a package
+
Index: /main/waeup.uniben/trunk/src/waeup/uniben/tests/test_configuration.py
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/tests/test_configuration.py	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/tests/test_configuration.py	(revision 8020)
@@ -0,0 +1,76 @@
+## $Id$
+##
+## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+"""Tests for configuration containers and related.
+"""
+from zope.component.interfaces import IFactory
+from zope.interface import verify
+from waeup.kofa.testing import FunctionalTestCase
+from waeup.kofa.configuration import ConfigurationContainer
+from waeup.kofa.interfaces import IConfigurationContainer
+from waeup.kofa.tests.test_configuration import (
+    ConfigurationTest, SessionConfigurationFactoryTest)
+from waeup.uniben.configuration import (
+    SessionConfiguration, SessionConfigurationFactory)
+from waeup.uniben.interfaces import(
+    ISessionConfiguration, ISessionConfigurationAdd)
+from waeup.uniben.testing import FunctionalLayer
+
+class ConfigurationTest(ConfigurationTest):
+
+    layer = FunctionalLayer
+
+    def setUp(self):
+        super(ConfigurationTest, self).setUp()
+        self.confcontainer = ConfigurationContainer()
+        self.sessionconf = SessionConfiguration()
+        return
+
+    #def tearDown(self):
+    #    super(ConfigurationTest, self).tearDown()
+    #    return
+
+    def test_interfaces(self):
+        #verify.verifyClass(IConfigurationContainer, ConfigurationContainer)
+        #verify.verifyObject(IConfigurationContainer, self.confcontainer)
+        verify.verifyClass(ISessionConfiguration, SessionConfiguration)
+        verify.verifyObject(ISessionConfiguration, self.sessionconf)
+        verify.verifyClass(ISessionConfigurationAdd, SessionConfiguration)
+        verify.verifyObject(ISessionConfigurationAdd, self.sessionconf)
+
+class SessionConfigurationFactoryTest(SessionConfigurationFactoryTest):
+
+    layer = FunctionalLayer
+
+    def setUp(self):
+        super(SessionConfigurationFactoryTest, self).setUp()
+        self.factory = SessionConfigurationFactory()
+
+    #def tearDown(self):
+    #    super(SessionConfigurationFactoryTest, self).tearDown()
+
+    def test_interfaces(self):
+        verify.verifyClass(IFactory, SessionConfigurationFactory)
+        verify.verifyObject(IFactory, self.factory)
+
+    def test_factory(self):
+        obj = self.factory()
+        assert isinstance(obj, SessionConfiguration)
+
+    def test_getInterfaces(self):
+        implemented_by = self.factory.getInterfaces()
+        assert implemented_by.isOrExtends(ISessionConfiguration)
Index: /main/waeup.uniben/trunk/src/waeup/uniben/tests/test_datacenter.py
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/tests/test_datacenter.py	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/tests/test_datacenter.py	(revision 8020)
@@ -0,0 +1,30 @@
+## $Id$
+##
+## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+from waeup.kofa.datacenter import DataCenter
+from waeup.kofa.testing import FunctionalTestCase
+from waeup.uniben.testing import FunctionalLayer
+
+
+class DatacenterTests(FunctionalTestCase):
+
+    layer = FunctionalLayer
+
+    def test_storage_path(self):
+        mydatacenter = DataCenter()
+        storagepath = mydatacenter.storage
+        self.assertTrue('/parts/test/datacenter' in storagepath)
Index: /main/waeup.uniben/trunk/src/waeup/uniben/tests/test_smtp.py
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/tests/test_smtp.py	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/tests/test_smtp.py	(revision 8020)
@@ -0,0 +1,129 @@
+# -*- coding: utf-8 -*-
+## $Id$
+##
+## Copyright (C) 2012 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+# Tests for email-related components
+import base64
+import logging
+import tempfile
+import shutil
+import unittest
+from StringIO import StringIO
+from zope.component import getUtility
+from zope.component.hooks import setSite
+from zope.sendmail.interfaces import IMailDelivery
+from waeup.kofa.app import University
+from waeup.kofa.interfaces import IMailService
+from waeup.kofa.smtp import (
+    encode_header_item, encode_address, encode_body, FakeSMTPDelivery,
+    send_mail)
+from waeup.kofa.testing import FunctionalTestCase
+from waeup.uniben.testing import FunctionalLayer
+
+#
+# SMTP test config
+#
+
+# Also run tests that send mail to external servers?
+#   If you enable this, please make sure the external smtp hosts and receivers
+#   do exist really and are not bothered by being spammed by a test programme.
+EXTERNAL_MAIL_TESTS = False
+
+# Maybe existing receiver of externally sent mail. If this mail account
+# exists really, it might receive mail from the tests!
+EXTERNAL_MAIL_RECEIVER = 'existing_address@waeup.org'
+
+# Names of mail deliveries to use when external mail tests are enabled.
+#   See local mail.zcml for names of available deliveries.
+EXTERNAL_DIRECT_DELIVERY = 'Direct SMTP on mail server'
+EXTERNAL_QUEUED_DELIVERY = 'Queued SMTP on mail server'
+
+#
+# end of SMTP test config
+#
+
+def external_mail_test(func):
+    if not EXTERNAL_MAIL_TESTS:
+        myself = __file__
+        if myself.endswith('.pyc'):
+            myself = myself[:-2]
+        print "WARNING: external mail tests are skipped!"
+        print "WARNING: edit %s to enable them." % myself
+        return
+    return func
+
+class ExternalMailerTests(FunctionalTestCase):
+
+    layer = FunctionalLayer
+
+    def setUp(self):
+        super(ExternalMailerTests, self).setUp()
+        # Setup a sample site for each test
+        app = University()
+        self.dc_root = tempfile.mkdtemp()
+        app['datacenter'].setStoragePath(self.dc_root)
+
+        # Prepopulate the ZODB...
+        self.getRootFolder()['app'] = app
+        self.app = self.getRootFolder()['app']
+        return
+
+    def tearDown(self):
+        super(ExternalMailerTests, self).tearDown()
+        shutil.rmtree(self.dc_root)
+        return
+
+    def test_config_default_mailer(self):
+        # The default mailer set in config is 'no mailer'.
+        self.assertEqual(
+            getattr(self.app.get('configuration', None), 'smtp_mailer'),
+            u'No email service')
+        return
+
+    @external_mail_test
+    def test_send_direct_mail(self):
+        # send mail using direct mail delivery
+        self.app['configuration'].smtp_mailer = EXTERNAL_DIRECT_DELIVERY
+        setSite(self.app)
+        result = send_mail(
+            'test program', 'no-reply@waeup.org',
+            'test mail receiver', EXTERNAL_MAIL_RECEIVER,
+            'Test Mail from WAeUP Kofa',
+            'Hi from test mailer!\n\nRegards,\nTest Programme\n'
+            )
+        import transaction
+        transaction.commit() # The mail is really sent when transactions is
+                             # committed
+        self.assertTrue('@' in result)
+        return
+
+    @external_mail_test
+    def test_send_queued_mail(self):
+        # send mail using queued mail delivery
+        self.app['configuration'].smtp_mailer = EXTERNAL_QUEUED_DELIVERY
+        setSite(self.app)
+        result = send_mail(
+            'test program', 'no-reply@waeup.org',
+            'test mail receiver', EXTERNAL_MAIL_RECEIVER,
+            'Test Mail from WAeUP Kofa',
+            'Hi from test mailer!\n\nRegards,\nTest Programme\n'
+            )
+        import transaction
+        transaction.commit() # The mail is really sent when transactions is
+                             # committed
+        self.assertTrue('@' in result)
+        return
Index: /main/waeup.uniben/trunk/src/waeup/uniben/university/__init__.py
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/university/__init__.py	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/university/__init__.py	(revision 8020)
@@ -0,0 +1,1 @@
+# Make this a package.
Index: /main/waeup.uniben/trunk/src/waeup/uniben/utils/__init__.py
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/utils/__init__.py	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/utils/__init__.py	(revision 8020)
@@ -0,0 +1,1 @@
+# Make this a package.
Index: /main/waeup.uniben/trunk/src/waeup/uniben/utils/tests.py
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/utils/tests.py	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/utils/tests.py	(revision 8020)
@@ -0,0 +1,29 @@
+## $Id$
+##
+## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+from waeup.kofa.testing import FunctionalTestCase
+from waeup.uniben.testing import FunctionalLayer
+
+
+class CustomPackageUtilsTests(FunctionalTestCase):
+
+    layer = FunctionalLayer
+
+    def test_foo(self):
+        '''Test method foo.
+        '''
+        return
Index: /main/waeup.uniben/trunk/src/waeup/uniben/utils/utils.py
===================================================================
--- /main/waeup.uniben/trunk/src/waeup/uniben/utils/utils.py	(revision 8020)
+++ /main/waeup.uniben/trunk/src/waeup/uniben/utils/utils.py	(revision 8020)
@@ -0,0 +1,205 @@
+## $Id$
+##
+## Copyright (C) 2012 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+"""Customize general helper utilities for Kofa.
+"""
+import os
+from waeup.kofa.utils.utils import KofaUtils
+from waeup.kofa.accesscodes import create_accesscode
+from waeup.uniben.interfaces import MessageFactory as _
+
+def actions_after_payment(student, payment, view):
+    if payment.p_category == 'clearance':
+        # Create CLR access code
+        pin, error = create_accesscode('CLR',0,student.student_id)
+        if error:
+            view.flash(_('Valid callback received. ${a}',
+                mapping = {'a':error}))
+            return
+        payment.ac = pin
+    elif payment.p_category == 'schoolfee':
+        # Create SFE access code
+        pin, error = create_accesscode('SFE',0,student.student_id)
+        if error:
+            view.flash(_('Valid callback received. ${a}',
+                mapping = {'a':error}))
+            return
+        payment.ac = pin
+    elif payment.p_category == 'bed_allocation':
+        # Create HOS access code
+        pin, error = create_accesscode('HOS',0,student.student_id)
+        if error:
+            view.flash(_('Valid callback received. ${a}',
+                mapping = {'a':error}))
+            return
+        payment.ac = pin
+    view.flash(_('Valid callback received.'))
+    return
+
+
+class KofaUtils(KofaUtils):
+    """A collection of methods subject to customization.
+    """
+    PORTAL_LANGUAGE = 'en'
+
+    PREFERRED_LANGUAGES_DICT = {
+        'en':(1, u'English'),
+        'fr':(2, u'Fran&ccedil;ais'),
+        'de':(3, u'Deutsch'),
+        'ha':(4, u'Hausa'),
+        'yo':(5, u'Yoruba'),
+        'ig':(6, u'Igbo'),
+        }
+
+    EXAM_SUBJECTS_DICT = {
+        'accounts': 'Accounts',
+        'add_general_science': 'Add General Science',
+        'add_maths': 'Add Maths',
+        'agricultural_science': 'Agricultural Science',
+        'applied_electricity': 'Applied Electricity',
+        'arabi': 'Arabic',
+        'arithmetics': 'Arithmetics',
+        'art': 'Art',
+        'auto_mechanics': 'Auto Mechanics',
+        'basic_electricity': 'Basic Electricity',
+        'bible_knowledge': 'Bible Knowledge',
+        'biology': 'Biology',
+        'biology_alt_syl': 'Biology (Alt.Syl)',
+        'book_keeping': 'Book Keeping',
+        'building_construction': 'Building Construction',
+        'business_management': 'Business Management',
+        'business_construction_management': 'Business/Construction Management (Adv)',
+        'chemistry': 'Chemistry',
+        'chemistry_alt_syl': 'Chemistry (Alt.Syl)',
+        'christian_religious_studies': 'Christian Religious Studies',
+        'clerical_office_duties': 'Clerical Office Duties',
+        'clothing_and_textiles': 'Clothing and Textiles',
+        'commerce': 'Commerce',
+        'economics': 'Economics',
+        'education': 'Education',
+        'efik': 'Efik',
+        'electronics': 'Electronics',
+        'elementary_surveying': 'Elementary Surveying',
+        'english_language': 'English Language',
+        'engineering_science': 'Engineering Science',
+        'financial_accounting': 'Financial Accounting',
+        'food_and_nutrition': 'Food and Nutrition',
+        'french': 'French',
+        'further_mathematics': 'Further Mathematics',
+        'general_science': 'General Science',
+        'geography': 'Geography',
+        'german': 'German',
+        'government': 'Government',
+        'hausa': 'Hausa',
+        'hausa_literature': 'Hausa Literature',
+        'health_science': 'Health Science',
+        'history': 'History',
+        'home_management': 'Home Management',
+        'industrial_electrical_installation': 'Industrial Elect Installation (Adv)',
+        'intergrated_science': 'Intergrated Science',
+        'islamic_studies': 'Islamic Studies',
+        'literature_in_english': 'Literature in English',
+        'literature_in_nigerian_languages': 'Literature in Nigerian Languages',
+        'igbo': 'Igbo',
+        'igbo_literature': 'Igbo Literature',
+        'yoruba': 'Yoruba',
+        'yoruba_literature': 'Yoruba Literature',
+        'management_in_living': 'Management in Living',
+        'mathematics': 'Mathematics',
+        'metalwork': 'Metalwork',
+        'music': 'Music',
+        'nigerian_language': 'Nigerian Language',
+        'office_practice': 'Office Practice',
+        'physical_education': 'Physical Education',
+        'physical_health_education': 'Physical and Health Education',
+        'physics': 'Physics',
+        'physics_alt_syl': 'Physics (Alt.Syl)',
+        'principles_of_cost_accounting': 'Principles of Cost Accounting',
+        'rural_science': 'Rural Science',
+        'science': 'Science',
+        'secretarial_duties': 'Secretarial Duties',
+        'shorthand': 'Shorthand',
+        'sierra_leone_studies': 'Sierra Leone Studies',
+        'sierra_leone_languages': 'Sierra Leone Languages',
+        'social_studies': 'Social Studies',
+        'statistics': 'Statistics',
+        'teaching_practice': 'Teaching Practice',
+        'technical_drawing': 'Technical Drawing',
+        'typewriting': 'Typewriting',
+        'visual_art': 'Visual Art',
+        'woodwork': 'Woodwork',
+        'winding_elect_machines': 'Winding of Elect Machines and Elect Eng Sc'
+        }
+
+    EXAM_GRADES = (
+        ('A1', 'Excellent (A1)'),
+        ('A2', 'Very Good (A2)'),
+        ('A3', 'Good (A3)'),
+        ('B2', 'Very Good (B2)'),
+        ('B3', 'Good (B3)'),
+        ('C4', 'Credit (C4)'),
+        ('C5', 'Credit (C5)'),
+        ('C6', 'Credit (C6)'),
+        ('D7', 'Pass (D7)'),
+        ('E8', 'Pass (E8)'),
+        ('F9', 'Fail (F9)'),
+        ('Aa', 'A (a)'),
+        ('Bb', 'B (b)'),
+        ('Cc', 'C (c)'),
+        ('Dd', 'D (d)'),
+        ('Ee', 'E (e)'),
+        ('Ff', 'F (f)'),
+        ('Gg', 'G (g)')
+        )
+
+    INST_TYPES_DICT = {
+        'faculty': 'Faculty of',
+        'department': 'Department of',
+        'school': 'School of',
+        'office': 'Office for',
+        'centre': 'Centre for',
+        'institute': 'Institute of',
+        'school_for': 'School for',
+        }
+
+    STUDY_MODES_DICT = {
+        'rmd_ft': 'Remedial with deficiencies',
+        'dp_pt': 'Diploma Part Time',
+        'ct_ft': 'Certificate Full Time',
+        'dp_ft': 'Diploma Full Time',
+        'de_pt': 'Direct Entry Part Time',
+        'pg_ft': 'Postgraduate Full Time',
+        'pg_pt': 'Postgraduate Part Time',
+        'jm_ft': 'Joint Matriculation Full Time',
+        'ume_ft': 'UME Full Time',
+        'de_ft': 'Direct Entry Full Time',
+        'ph_ft': 'Post Higher Education Full Time',
+        'transfer_pt': 'Transfer Part Time',
+        'ug_pt': 'Undergraduate Part Time',
+        'transfer_ft': 'Transfer Full Time',
+        'ct_pt': 'Certificate Part Time',
+        'ug_ft': 'Undergraduate Full Time',
+        'rm_ft': 'Remedial'
+        }
+
+    APP_CATS_DICT = {
+        'basic': 'PUME, PDE, PCE, PRENCE',
+        'no': 'no application',
+        'pg': 'Postgraduate',
+        'sandwich': 'Sandwich',
+        'cest': 'Part-Time, Diploma, Certificate'
+        }
