Index: /main/waeup.kwarapoly/trunk/README-zeo.txt
===================================================================
--- /main/waeup.kwarapoly/trunk/README-zeo.txt	(revision 9346)
+++ /main/waeup.kwarapoly/trunk/README-zeo.txt	(revision 9347)
@@ -1,3 +1,3 @@
-Deploying waeup.fceokene as ZEO-install
+Deploying waeup.kwarapoly as ZEO-install
 *************************************
 
@@ -14,5 +14,5 @@
 ========================
 
-To install waeup.fceokene ZEO-based you can run `buildout` with the given
+To install waeup.kwarapoly ZEO-based you can run `buildout` with the given
 (or your own) configuration file like this::
 
Index: /main/waeup.kwarapoly/trunk/buildout-windows.cfg
===================================================================
--- /main/waeup.kwarapoly/trunk/buildout-windows.cfg	(revision 9346)
+++ /main/waeup.kwarapoly/trunk/buildout-windows.cfg	(revision 9347)
@@ -25,5 +25,5 @@
 # This creates only the paster script in bin/.
 recipe = zc.recipe.egg
-eggs = waeup.fceokene
+eggs = waeup.kwarapoly
        z3c.evalexception>=2.0
        Paste
Index: /main/waeup.kwarapoly/trunk/buildout-zeo.cfg
===================================================================
--- /main/waeup.kwarapoly/trunk/buildout-zeo.cfg	(revision 9346)
+++ /main/waeup.kwarapoly/trunk/buildout-zeo.cfg	(revision 9347)
@@ -112,5 +112,5 @@
 # See http://pypi.python.org/pypi/zc.recipe.egg for details...
 recipe = zc.recipe.egg
-eggs = waeup.fceokene [beaker]
+eggs = waeup.kwarapoly [beaker]
 arguments = "${buildout:parts-directory}/etc/zeo1.conf"
 scripts = kofactl=zeo_client1
@@ -119,5 +119,5 @@
 # See http://pypi.python.org/pypi/zc.recipe.egg for details...
 recipe = zc.recipe.egg
-eggs = waeup.fceokene [beaker]
+eggs = waeup.kwarapoly [beaker]
 arguments = "${buildout:parts-directory}/etc/zeo2.conf"
 scripts = kofactl=zeo_client2
Index: /main/waeup.kwarapoly/trunk/buildout.cfg
===================================================================
--- /main/waeup.kwarapoly/trunk/buildout.cfg	(revision 9346)
+++ /main/waeup.kwarapoly/trunk/buildout.cfg	(revision 9347)
@@ -47,9 +47,9 @@
 host = 0.0.0.0
 # default is 8080
-base_port = 7031
-zeo1_port = 7031
-zeo2_port = 7032
-zeo3_port = 7033
-server_port = 7030
+base_port = 7051
+zeo1_port = 7051
+zeo2_port = 7052
+zeo3_port = 7053
+server_port = 7050
 # default 7. Number of pre-opened ZODB threads/client
 pool_size = 7
@@ -64,5 +64,5 @@
 # This creates all scripts in bin/. The kofactl created here is 'faulty'.
 recipe = zc.recipe.egg
-eggs = waeup.fceokene [beaker]
+eggs = waeup.kwarapoly [beaker]
        z3c.evalexception>=2.0
        Paste
@@ -78,5 +78,5 @@
 # fixed path to zdaemon.conf as argument.
 recipe = zc.recipe.egg
-eggs = waeup.fceokene [beaker]
+eggs = waeup.kwarapoly [beaker]
 arguments = "${buildout:parts-directory}/etc/zdaemon.conf"
 scripts = kofactl=kofactl
@@ -93,5 +93,5 @@
 [test]
 recipe = zc.recipe.testrunner
-eggs = waeup.fceokene [beaker, test]
+eggs = waeup.kwarapoly [beaker, test]
 defaults = ['--tests-pattern', '^f?tests$', '-v']
 
@@ -99,8 +99,8 @@
 [i18n]
 recipe = z3c.recipe.i18n:i18n
-packages = waeup.fceokene
-eggs = waeup.fceokene
-domain = waeup.fceokene
-output = src/waeup/fceokene/locales
+packages = waeup.kwarapoly
+eggs = waeup.kwarapoly
+domain = waeup.kwarapoly
+output = src/waeup/kwarapoly/locales
 zcml =
 
@@ -109,5 +109,5 @@
 [zpasswd]
 recipe = z3c.recipe.dev:script
-eggs = waeup.fceokene
+eggs = waeup.kwarapoly
 module = zope.app.server.zpasswd
 method = main
@@ -166,5 +166,5 @@
 [waeupdocs]
 recipe = collective.recipe.sphinxbuilder
-eggs = waeup.fceokene[docs]
+eggs = waeup.kwarapoly[docs]
 source = ${buildout:directory}/docs/source
 build = ${buildout:directory}/docs/build
@@ -173,5 +173,5 @@
 [coverage-detect]
 recipe = zc.recipe.testrunner
-eggs = waeup.fceokene[beaker]
+eggs = waeup.kwarapoly[beaker]
 defaults = ['--tests-pattern', '^f?tests$', '-v', '--coverage', 'coverage']
 
Index: /main/waeup.kwarapoly/trunk/etc/debug.ini.in
===================================================================
--- /main/waeup.kwarapoly/trunk/etc/debug.ini.in	(revision 9346)
+++ /main/waeup.kwarapoly/trunk/etc/debug.ini.in	(revision 9347)
@@ -54,5 +54,5 @@
 
 [app:zope]
-use = egg:waeup.fceokene#debug
+use = egg:waeup.kwarapoly#debug
 filter-with = translogger
 exempt-exceptions = zope.security.interfaces.IUnauthorized
Index: /main/waeup.kwarapoly/trunk/etc/deploy.ini.in
===================================================================
--- /main/waeup.kwarapoly/trunk/etc/deploy.ini.in	(revision 9346)
+++ /main/waeup.kwarapoly/trunk/etc/deploy.ini.in	(revision 9347)
@@ -48,5 +48,5 @@
 
 [app:main]
-use = egg:waeup.fceokene
+use = egg:waeup.kwarapoly
 filter-with = translogger
 
Index: /main/waeup.kwarapoly/trunk/etc/profile.ini.in
===================================================================
--- /main/waeup.kwarapoly/trunk/etc/profile.ini.in	(revision 9346)
+++ /main/waeup.kwarapoly/trunk/etc/profile.ini.in	(revision 9347)
@@ -52,5 +52,5 @@
 
 [app:app]
-use = egg:waeup.fceokene
+use = egg:waeup.kwarapoly
 
 [pipeline:main]
Index: /main/waeup.kwarapoly/trunk/etc/site.zcml.in
===================================================================
--- /main/waeup.kwarapoly/trunk/etc/site.zcml.in	(revision 9346)
+++ /main/waeup.kwarapoly/trunk/etc/site.zcml.in	(revision 9347)
@@ -1,8 +1,8 @@
 <configure xmlns="http://namespaces.zope.org/zope"
            xmlns:kofa="http://namespaces.waeup.org/kofa"
-           i18n_domain="waeup.fceokene">
+           i18n_domain="waeup.kwarapoly">
 
-  <include package="waeup.fceokene" />
-  <include package="waeup.fceokene" file="mail.zcml" />
+  <include package="waeup.kwarapoly" />
+  <include package="waeup.kwarapoly" file="mail.zcml" />
   <!-- install job container
 
@@ -28,5 +28,5 @@
       path="${buildout:directory}/var/datacenter" />
 
-    <configure i18n_domain="waeup.fceokene">
+    <configure i18n_domain="waeup.kwarapoly">
 
 
Index: /main/waeup.kwarapoly/trunk/etc/zeo1.ini.in
===================================================================
--- /main/waeup.kwarapoly/trunk/etc/zeo1.ini.in	(revision 9346)
+++ /main/waeup.kwarapoly/trunk/etc/zeo1.ini.in	(revision 9347)
@@ -48,5 +48,5 @@
 
 [app:main]
-use = egg:waeup.fceokene
+use = egg:waeup.kwarapoly
 filter-with = translogger
 
Index: /main/waeup.kwarapoly/trunk/etc/zeo2.ini.in
===================================================================
--- /main/waeup.kwarapoly/trunk/etc/zeo2.ini.in	(revision 9346)
+++ /main/waeup.kwarapoly/trunk/etc/zeo2.ini.in	(revision 9347)
@@ -48,5 +48,5 @@
 
 [app:main]
-use = egg:waeup.fceokene
+use = egg:waeup.kwarapoly
 filter-with = translogger
 
Index: /main/waeup.kwarapoly/trunk/etc/zope_zeo1.conf.in
===================================================================
--- /main/waeup.kwarapoly/trunk/etc/zope_zeo1.conf.in	(revision 9346)
+++ /main/waeup.kwarapoly/trunk/etc/zope_zeo1.conf.in	(revision 9347)
@@ -24,5 +24,5 @@
 # Uncomment this if you want to connect to a ZEO server instead:
   <zeoclient>
-    server localhost:7030
+    server localhost:7050
     storage 1
     # ZEO client cache, in bytes
@@ -63,5 +63,5 @@
 <zodb async>
   <zeoclient>
-    server localhost:7030
+    server localhost:7050
     storage async
     name async
Index: /main/waeup.kwarapoly/trunk/etc/zope_zeo2.conf.in
===================================================================
--- /main/waeup.kwarapoly/trunk/etc/zope_zeo2.conf.in	(revision 9346)
+++ /main/waeup.kwarapoly/trunk/etc/zope_zeo2.conf.in	(revision 9347)
@@ -24,5 +24,5 @@
 # Uncomment this if you want to connect to a ZEO server instead:
   <zeoclient>
-    server localhost:7030
+    server localhost:7050
     storage 1
     # ZEO client cache, in bytes
@@ -63,5 +63,5 @@
 <zodb async>
   <zeoclient>
-    server localhost:7030
+    server localhost:7050
     storage async
     name async
Index: /main/waeup.kwarapoly/trunk/setup.py
===================================================================
--- /main/waeup.kwarapoly/trunk/setup.py	(revision 9346)
+++ /main/waeup.kwarapoly/trunk/setup.py	(revision 9347)
@@ -44,5 +44,5 @@
     read('README.txt')
     + '\n\n'
-    + read('src', 'waeup', 'fceokene', 'README.txt')
+    + read('src', 'waeup', 'kwarapoly', 'README.txt')
     + '\n\n'
     + read('CHANGES.txt')
@@ -52,5 +52,5 @@
     )
 
-setup(name = 'waeup.fceokene',
+setup(name = 'waeup.kwarapoly',
       version = version,
       description = "A customized waeup.kofa",
@@ -90,5 +90,5 @@
       # Add entry points here
       [hurry.resource.libraries]
-      waeup_custom = waeup.fceokene.browser.resources:waeup_custom
+      waeup_custom = waeup.kwarapoly.browser.resources:waeup_custom
       [console_scripts]
       kofa-debug = grokcore.startup:interactive_debug_prompt
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/README.txt
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/README.txt	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/README.txt	(revision 9347)
@@ -0,0 +1,4 @@
+The waeup.kwarapoly package
+************************
+
+A customized portal software for student registration.
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/__init__.py
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/__init__.py	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/__init__.py	(revision 9347)
@@ -0,0 +1,1 @@
+# Make this a package.
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/applicants/__init__.py
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/applicants/__init__.py	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/applicants/__init__.py	(revision 9347)
@@ -0,0 +1,1 @@
+# Make this a package.
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/applicants/applicant.py
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/applicants/applicant.py	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/applicants/applicant.py	(revision 9347)
@@ -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
+##
+
+import grok
+from zope.interface import implementedBy
+from waeup.kofa.applicants.applicant import ApplicantFactory
+from waeup.kofa.utils.helpers import attrs_to_fields
+from kofacustom.nigeria.applicants.applicant import NigeriaApplicant
+from waeup.kwarapoly.applicants.interfaces import(
+    ICustomApplicant, ICustomUGApplicantEdit, ICustomPGApplicantEdit, IPUTMEApplicantEdit)
+
+class CustomApplicant(NigeriaApplicant):
+
+    grok.implements(ICustomApplicant, ICustomUGApplicantEdit,
+        ICustomPGApplicantEdit, IPUTMEApplicantEdit)
+    grok.provides(ICustomApplicant)
+
+# Set all attributes of CustomApplicant required in ICustomApplicant 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.
+CustomApplicant = attrs_to_fields(CustomApplicant)
+
+class CustomApplicantFactory(ApplicantFactory):
+    """A factory for customized applicants.
+    """
+
+    def __call__(self, *args, **kw):
+        return CustomApplicant()
+
+    def getInterfaces(self):
+        return implementedBy(CustomApplicant)
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/applicants/interfaces.py
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/applicants/interfaces.py	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/applicants/interfaces.py	(revision 9347)
@@ -0,0 +1,124 @@
+## $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 (
+    IApplicantBaseData,
+    AppCatCertificateSource, CertificateSource)
+from waeup.kofa.schoolgrades import ResultEntryField
+from waeup.kofa.interfaces import (
+    SimpleKofaVocabulary, academic_sessions_vocab, validate_email)
+from waeup.kofa.schema import FormattedDate, TextLineChoice
+from waeup.kofa.students.vocabularies import nats_vocab, GenderSource
+from waeup.kofa.applicants.interfaces import contextual_reg_num_source
+from kofacustom.nigeria.applicants.interfaces import (
+    LGASource, high_qual, high_grade, exam_types,
+    INigeriaUGApplicant, INigeriaPGApplicant,
+    INigeriaApplicantOnlinePayment,
+    INigeriaUGApplicantEdit, INigeriaPGApplicantEdit,
+    INigeriaApplicantUpdateByRegNo,
+    IPUTMEApplicantEdit,
+    )
+from waeup.kwarapoly.interfaces import MessageFactory as _
+from waeup.kwarapoly.payments.interfaces import ICustomOnlinePayment
+
+class ICustomUGApplicant(INigeriaUGApplicant):
+    """An undergraduate applicant.
+
+    This interface defines the least common multiple of all fields
+    in ug application forms. In customized forms, fields can be excluded by
+    adding them to the UG_OMIT* tuples.
+    """
+
+class ICustomPGApplicant(INigeriaPGApplicant):
+    """A postgraduate applicant.
+
+    This interface defines the least common multiple of all fields
+    in pg application forms. In customized forms, fields can be excluded by
+    adding them to the PG_OMIT* tuples.
+    """
+
+class ICustomApplicant(ICustomUGApplicant, ICustomPGApplicant):
+    """An interface for both types of applicants.
+
+    Attention: The ICustomPGApplicant field seetings will be overwritten
+    by ICustomPGApplicant field settings. If a field is defined
+    in both interfaces zope.schema validates only against the
+    constraints in ICustomUGApplicant. This does not affect the forms
+    since they are build on either ICustomUGApplicant or ICustomPGApplicant.
+    """
+
+    def writeLogMessage(view, comment):
+        """Adds an INFO message to the log file
+        """
+
+    def createStudent():
+        """Create a student object from applicatnt data
+        and copy applicant object.
+        """
+
+class ICustomUGApplicantEdit(INigeriaUGApplicantEdit):
+    """An undergraduate applicant interface for edit forms.
+
+    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 ICustomPGApplicantEdit(INigeriaPGApplicantEdit):
+    """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.
+    """
+
+class ICustomApplicantOnlinePayment(INigeriaApplicantOnlinePayment):
+    """An applicant payment via payment gateways.
+
+    """
+
+class IPUTMEApplicantEdit(IPUTMEApplicantEdit):
+    """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 ICustomApplicantUpdateByRegNo(INigeriaApplicantUpdateByRegNo):
+    """Representation of an applicant.
+
+    Skip regular reg_number validation if reg_number is used for finding
+    the applicant object.
+    """
+
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/applicants/payment.py
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/applicants/payment.py	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/applicants/payment.py	(revision 9347)
@@ -0,0 +1,58 @@
+## $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
+##
+"""
+Application fee payment.
+"""
+import grok
+from zope.component.interfaces import IFactory
+from zope.interface import implementedBy
+from waeup.kofa.applicants.payment import ApplicantOnlinePayment
+from waeup.kofa.utils.helpers import attrs_to_fields
+from waeup.kwarapoly.applicants.interfaces import ICustomApplicantOnlinePayment
+
+class CustomApplicantOnlinePayment(ApplicantOnlinePayment):
+    """This is a custom online payment for applicants.
+
+    Since this class inherits from ApplicantOnlinePayment, it automatically
+    implements the interfaces of the parent class which means
+    all viewlets and views meant for StudentOnlinePayment
+    can also be accessed in the customized system.
+    """
+    grok.implements(ICustomApplicantOnlinePayment)
+    grok.provides(ICustomApplicantOnlinePayment)
+
+    def __init__(self):
+        super(CustomApplicantOnlinePayment, self).__init__()
+        return
+
+CustomApplicantOnlinePayment = attrs_to_fields(CustomApplicantOnlinePayment)
+
+# Applicant online payments must be importable. So we might need a factory.
+class CustomApplicantOnlinePaymentFactory(grok.GlobalUtility):
+    """A factory for applicant online payments.
+    """
+    grok.implements(IFactory)
+    grok.name(u'waeup.ApplicantOnlinePayment')
+    title = u"Create a new online payment.",
+    description = u"This factory instantiates new online payment instances."
+
+    def __call__(self, *args, **kw):
+        return CustomApplicantOnlinePayment()
+
+    def getInterfaces(self):
+        return implementedBy(CustomApplicantOnlinePayment)
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/applicants/tests/__init__.py
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/applicants/tests/__init__.py	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/applicants/tests/__init__.py	(revision 9347)
@@ -0,0 +1,1 @@
+# Make this a package.
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/applicants/tests/test_applicant.py
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/applicants/tests/test_applicant.py	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/applicants/tests/test_applicant.py	(revision 9347)
@@ -0,0 +1,79 @@
+## $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 applicants and related.
+"""
+import grok
+import shutil
+import tempfile
+from StringIO import StringIO
+from zope.component import adapts, queryUtility
+from zope.component.interfaces import IFactory
+from zope.interface import verify, implements
+from zope.location.interfaces import ILocation
+from waeup.kofa.image.interfaces import IKofaImageFile
+from waeup.kofa.imagestorage import DefaultStorage
+from waeup.kofa.interfaces import IFileStoreHandler, IFileStoreNameChooser
+from waeup.kofa.testing import FunctionalTestCase
+from waeup.kwarapoly.applicants.applicant import (
+    CustomApplicant, CustomApplicantFactory,
+    )
+from waeup.kwarapoly.applicants.interfaces import ICustomApplicant
+from waeup.kwarapoly.testing import FunctionalLayer
+
+class CustomApplicantTest(FunctionalTestCase):
+
+    layer = FunctionalLayer
+
+    def setUp(self):
+        super(CustomApplicantTest, self).setUp()
+        self.applicant = CustomApplicant()
+        return
+
+    def tearDown(self):
+        super(CustomApplicantTest, self).tearDown()
+        return
+
+    def test_interfaces(self):
+        verify.verifyClass(ICustomApplicant, CustomApplicant)
+        verify.verifyObject(ICustomApplicant, self.applicant)
+        return
+
+class CustomApplicantFactoryTest(FunctionalTestCase):
+
+    layer = FunctionalLayer
+
+    def setUp(self):
+        super(CustomApplicantFactoryTest, self).setUp()
+        self.factory = CustomApplicantFactory()
+        return
+
+    def tearDown(self):
+        super(CustomApplicantFactoryTest, self).tearDown()
+        return
+
+    def test_interfaces(self):
+        verify.verifyClass(IFactory, CustomApplicantFactory)
+        verify.verifyObject(IFactory, self.factory)
+
+    def test_factory(self):
+        obj = self.factory()
+        assert isinstance(obj, CustomApplicant)
+
+    def test_getInterfaces(self):
+        implemented_by = self.factory.getInterfaces()
+        assert implemented_by.isOrExtends(ICustomApplicant)
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/applicants/utils.py
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/applicants/utils.py	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/applicants/utils.py	(revision 9347)
@@ -0,0 +1,45 @@
+## $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.
+"""
+
+from time import time
+import sys
+import grok
+from kofacustom.nigeria.applicants.utils import NigeriaApplicantsUtils
+
+
+
+class ApplicantsUtils(NigeriaApplicantsUtils):
+    """A collection of parameters and methods subject to customization.
+    """
+
+    APP_TYPES_DICT = {
+        'pude': ['Post UDE Screening', 'PUDE'],
+        'prence': ['Pre-NCE Programme', 'PRE'],
+        'sandwich': ['NCE Sandwich Programme', 'SAND'],
+        'pt': ['Part-Time Degree Programmes', 'PTP'],
+        'putme': ['Post-UTME Screening Exercise', 'PUTME'],
+        'app': ['General Studies', 'APP'],
+        'cest': ['Common Entry Screening Test', 'CEST'],
+        'ct': ['Certificate Programmes', 'CTP'],
+        'dp': ['Diploma Programmes', 'DPP'],
+        'pce': ['PCE Screening', 'PCE'],
+        'pgft': ['Postgraduate Full-Time Programmes', 'PG'],
+        'pgpt': ['Postgraduate Part-Time Programmes', 'PG']
+        }
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/browser/__init__.py
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/browser/__init__.py	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/browser/__init__.py	(revision 9347)
@@ -0,0 +1,1 @@
+# Make this a package.
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/browser/pages.py
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/browser/pages.py	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/browser/pages.py	(revision 9347)
@@ -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 waeup.kofa.browser.pages import (
+    SessionConfigurationAddFormPage, SessionConfigurationManageFormPage,
+    CertificatePage, CertificateManageFormPage)
+from waeup.kofa.university.interfaces import ICertificate
+from waeup.kwarapoly.interfaces import (
+    ICustomSessionConfiguration, ICustomSessionConfigurationAdd)
+
+class CustomSessionConfigurationAddFormPage(SessionConfigurationAddFormPage):
+    """Add a session configuration object to configuration container.
+    """
+    form_fields = grok.AutoFields(ICustomSessionConfigurationAdd)
+
+class CustomSessionConfigurationManageFormPage(SessionConfigurationManageFormPage):
+    """Manage session configuration object.
+    """
+    form_fields = grok.AutoFields(ICustomSessionConfiguration)
+
+class CustomCertificatePage(CertificatePage):
+    """Index page for certificates.
+    """
+    form_fields = grok.AutoFields(ICertificate).omit(
+        'school_fee_1', 'school_fee_2', 'school_fee_3', 'school_fee_4')
+
+class CustomCertificateManageFormPage(CertificateManageFormPage):
+    """Manage the properties of a `Certificate` instance.
+    """
+    form_fields = grok.AutoFields(ICertificate).omit(
+        'school_fee_1', 'school_fee_2', 'school_fee_3', 'school_fee_4')
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/browser/pdf.py
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/browser/pdf.py	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/browser/pdf.py	(revision 9347)
@@ -0,0 +1,35 @@
+## $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
+##
+"""
+Reusable components for pdf generation.
+"""
+
+import os
+from waeup.kofa.browser.pdf import PDFCreator
+
+class CustomPDFCreator(PDFCreator):
+    """A utility to help with generating PDF docs.
+    """
+
+    #header_logo_path = os.path.join(
+    #    os.path.dirname(__file__), 'static', 'pdf_logo.jpg')
+    #watermark_path = os.path.join(
+    #    os.path.dirname(__file__), 'static', 'pdf_watermark.jpg')
+
+    #watermark_pos = [-25, 100]
+    #logo_pos = [507, 720, 40]
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/browser/resources.py
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/browser/resources.py	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/browser/resources.py	(revision 9347)
@@ -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
+##
+import grok
+from waeup.kofa.browser.resources import (
+    Library, ResourceInclusion, waeup_base_css,
+    )
+
+#: All local resources are registered under the name ``waeup_custom``.
+waeup_custom = Library('waeup_custom', 'static')
+
+custom_theme = ResourceInclusion(
+    waeup_custom, 'custom_theme.css',
+    depends = [waeup_base_css])
+
+favicon = ResourceInclusion(waeup_custom, 'favicon.ico')
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/browser/static/custom_theme.css
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/browser/static/custom_theme.css	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/browser/static/custom_theme.css	(revision 9347)
@@ -0,0 +1,89 @@
+/* Original colors, dark blue: #426ab2,light blue: #779ce2 */
+
+.topbar-inner, .topbar .fill {
+  background-color: #109445;
+  background-image: -moz-linear-gradient(center top , #095427, #109445);
+  background-image: -khtml-gradient(linear, left top, left bottom, from(#095427), to(#109445));
+  background-image: -moz-linear-gradient(top, #095427, #109445);
+  background-image: -ms-linear-gradient(top, #095427, #109445);
+  background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #095427), color-stop(100%, #109445));
+  background-image: -webkit-linear-gradient(top, #095427, #109445);
+  background-image: -o-linear-gradient(top, #095427, #109445);
+  background-image: linear-gradient(top, #095427, #109445);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#095427', endColorstr='#109445', GradientType=0);
+}
+
+.topbar div > ul .active > a, .nav .active > a {
+  background-color: #109445;
+}
+
+.topbar h3 a:hover, .topbar .brand:hover, .topbar ul .active > a {
+  background-color: #109445;
+}
+
+.topbar div > ul a:hover, .nav a:hover {
+  background-color: #109445;
+}
+
+
+.topbar div > ul .menu-dropdown,
+.nav .menu-dropdown,
+.topbar div > ul .dropdown-menu,
+.nav .dropdown-menu {
+  background-color: #109445;
+}
+
+.topbar div > ul a.menu:hover,
+.nav a.menu:hover,
+.topbar div > ul li.open .menu,
+.nav li.open .menu,
+.topbar div > ul .dropdown-toggle:hover,
+.nav .dropdown-toggle:hover,
+.topbar div > ul .dropdown.open .dropdown-toggle,
+.nav .dropdown.open .dropdown-toggle {
+  background: #109445;
+}
+
+/* Original background color */
+/*
+body {
+  background-color: #F6F6D9;
+}
+*/
+
+body {
+  background-color: #e6e8e7;
+}
+
+.topbar div > ul .menu-dropdown li a:hover,
+.nav .menu-dropdown li a:hover,
+.topbar div > ul .dropdown-menu li a:hover,
+.nav .dropdown-menu li a:hover {
+  background-color: #095427;
+  background-repeat: repeat-x;
+  background-image: -khtml-gradient(linear, left top, left bottom, from(#109445), to(#191919));
+  background-image: -moz-linear-gradient(top, #095427, #095427);
+  background-image: -ms-linear-gradient(top, #095427, #095427);
+  background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #095427), color-stop(100%, #095427));
+  background-image: -webkit-linear-gradient(top, #095427, #095427);
+  background-image: -o-linear-gradient(top, #095427, #095427);
+  background-image: linear-gradient(top, #095427, #095427);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#095427', endColorstr='#095427', GradientType=0);
+  color: #ffffff;
+}
+
+.topbar div > ul .menu-dropdown li a, .nav .menu-dropdown li a, .topbar div > ul .dropdown-menu li a, .nav .dropdown-menu li a {
+    color: #ffffff;
+}
+
+a {
+  color: #095427;
+}
+
+a:hover {
+  color: #109445;
+}
+
+.topbar a {
+    color: white;
+}
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/browser/tests.py
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/browser/tests.py	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/browser/tests.py	(revision 9347)
@@ -0,0 +1,58 @@
+## $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 zope.security.interfaces import Unauthorized
+from waeup.kofa.testing import FunctionalTestCase
+from waeup.kwarapoly.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('...KwaraPoly Theme...', self.browser.contents)
+        self.browser.getControl(name="form.skin").value = ['custom theme']
+        self.browser.getControl("Save").click()
+        self.browser.open('http://localhost/app/configuration')
+        return
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/browser/theming.py
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/browser/theming.py	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/browser/theming.py	(revision 9347)
@@ -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.kwarapoly.browser.resources import custom_theme, favicon
+
+from waeup.kwarapoly.interfaces import MessageFactory as _
+
+class CustomTheme(grok.GlobalUtility):
+    """A custom theme based on the Kofa base theme.
+    """
+    grok.implements(ITheme)
+    grok.name('custom theme')
+
+    description = _(u'KwaraPoly Theme')
+
+    def getResources(self):
+        return [custom_theme, favicon]
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/configuration.py
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/configuration.py	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/configuration.py	(revision 9347)
@@ -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.kwarapoly.interfaces import (
+    ICustomSessionConfiguration, ICustomSessionConfigurationAdd)
+
+
+class CustomSessionConfiguration(SessionConfiguration):
+    """
+    Customized session configuration model
+    """
+
+    grok.implements(ICustomSessionConfiguration, ICustomSessionConfigurationAdd)
+
+CustomSessionConfiguration = attrs_to_fields(CustomSessionConfiguration)
+
+class CustomSessionConfigurationFactory(SessionConfigurationFactory):
+    """A customized factory for session configuration objects.
+    """
+
+    def __call__(self, *args, **kw):
+        return CustomSessionConfiguration(*args, **kw)
+
+    def getInterfaces(self):
+        return implementedBy(CustomSessionConfiguration)
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/configure.zcml
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/configure.zcml	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/configure.zcml	(revision 9347)
@@ -0,0 +1,11 @@
+<configure xmlns="http://namespaces.zope.org/zope"
+           xmlns:grok="http://namespaces.zope.org/grok">
+  <include package="waeup.kwarapoly" file="locales.zcml" />
+
+  <!-- kofacustom.nigeria configures everything we basically need.
+  -->
+  <include package="kofacustom.nigeria" />
+
+  <grok:grok package="." />
+
+</configure>
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/ftesting.zcml
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/ftesting.zcml	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/ftesting.zcml	(revision 9347)
@@ -0,0 +1,67 @@
+<configure
+   xmlns="http://namespaces.zope.org/zope"
+   xmlns:kofa="http://namespaces.waeup.org/kofa"
+   i18n_domain="waeup.custom"
+   package="waeup.kwarapoly"
+   >
+
+  <include package="waeup.kwarapoly" />
+  <include package="waeup.kwarapoly" 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.kwarapoly/trunk/src/waeup/kwarapoly/interfaces.py
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/interfaces.py	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/interfaces.py	(revision 9347)
@@ -0,0 +1,97 @@
+## $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 zc.sourcefactory.basic import BasicSourceFactory
+from waeup.kofa.interfaces import (SimpleKofaVocabulary,
+    ISessionConfiguration, academic_sessions_vocab)
+
+_ = MessageFactory = zope.i18nmessageid.MessageFactory('waeup.kwarapoly')
+
+# It's recommended to replicate all fields from the base package here.
+class ICustomSessionConfiguration(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,
+        )
+
+    application_fee = schema.Float(
+        title = _(u'Application Fee (fallback)'),
+        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,
+        )
+
+    # Additional fees in custom package
+
+    maint_fee = schema.Float(
+        title = _(u'Maintenance Fee (ignored)'),
+        default = 0.0,
+        required = False,
+        )
+
+    gown_fee = schema.Float(
+        title = _(u'Gown Fee'),
+        default = 0.0,
+        required = False,
+        )
+
+    transfer_fee = schema.Float(
+        title = _(u'Transfer Fee'),
+        default = 0.0,
+        required = False,
+        )
+
+    def getSessionString():
+        """Returns the session string from the vocabulary.
+        """
+
+
+class ICustomSessionConfigurationAdd(ICustomSessionConfiguration):
+    """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,
+        )
+
+ICustomSessionConfigurationAdd[
+    'academic_session'].order =  ICustomSessionConfiguration[
+    'academic_session'].order
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/interswitch/__init__.py
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/interswitch/__init__.py	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/interswitch/__init__.py	(revision 9347)
@@ -0,0 +1,1 @@
+# Make this a package.
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/interswitch/browser.py
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/interswitch/browser.py	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/interswitch/browser.py	(revision 9347)
@@ -0,0 +1,358 @@
+## $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 zope.component import getUtility
+from waeup.kofa.browser.layout import KofaPage, UtilityView
+from waeup.kofa.accesscodes import create_accesscode
+from waeup.kofa.interfaces import RETURNING, IKofaUtils
+from waeup.kofa.utils.helpers import to_timezone
+from waeup.kofa.students.viewlets import ApprovePaymentActionButton as APABStudent
+from waeup.kofa.applicants.viewlets import ApprovePaymentActionButton as APABApplicant
+from waeup.kofa.payments.interfaces import payment_categories
+from waeup.kwarapoly.students.interfaces import ICustomStudentOnlinePayment
+from waeup.kwarapoly.applicants.interfaces import ICustomApplicantOnlinePayment
+from waeup.kwarapoly.interfaces import MessageFactory as _
+
+PRODUCT_ID = '83'
+SITE_NAME = 'kwarapoly-kofa.waeup.org'
+PROVIDER_ACCT = '0026781725'
+PROVIDER_BANK_ID = '31'
+PROVIDER_ITEM_NAME = 'BT Education'
+INSTITUTION_NAME = 'KwaraPoly'
+CURRENCY = '566'
+#QUERY_URL = 'https://webpay.interswitchng.com/paydirect/services/TransactionQueryURL.aspx'
+#QUERY_URL = 'https://testwebpay.interswitchng.com/test_paydirect/services/TransactionQueryURL.aspx'
+POST_ACTION = 'https://webpay.interswitchng.com/paydirect/webpay/pay.aspx'
+#POST_ACTION = 'https://testwebpay.interswitchng.com/test_paydirect/webpay/pay.aspx'
+
+HOST = 'webpay.interswitchng.com'
+#HOST = 'testwebpay.interswitchng.com'
+URL = '/paydirect/services/TransactionQueryWs.asmx'
+#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
+
+def query_interswitch(payment):
+    sr = get_SOAP_response(PRODUCT_ID, payment.p_id)
+    wlist = sr.split(':')
+    if len(wlist) != 7:
+        msg = _('Invalid callback: ${a}', mapping = {'a': sr})
+        log = 'invalid callback for payment %s: %s' % (payment.p_id, sr)
+        return False, msg, log
+    payment.r_code = wlist[0]
+    payment.r_desc = wlist[1]
+    payment.r_amount_approved = float(wlist[2]) / 100
+    payment.r_card_num = wlist[3]
+    payment.r_pay_reference = wlist[5]
+    if payment.r_code != '00':
+        msg = _('Unsuccessful callback: ${a}', mapping = {'a': sr})
+        log = 'unsuccessful callback for payment %s: %s' % (payment.p_id, sr)
+        payment.p_state = 'failed'
+        return False, msg, log
+    if payment.r_amount_approved != payment.amount_auth:
+        msg = _('Callback amount does not match.')
+        log = 'wrong callback for payment %s: %s' % (payment.p_id, sr)
+        payment.p_state = 'failed'
+        return False, msg, log
+    if wlist[4] != payment.p_id:
+        msg = _('Callback transaction id does not match.')
+        log = 'wrong callback for payment %s: %s' % (payment.p_id, sr)
+        payment.p_state = 'failed'
+        return False, msg, log
+    payment.p_state = 'paid'
+    payment.payment_date = datetime.utcnow()
+    msg = _('Successful callback received')
+    log = 'valid callback for payment %s: %s' % (payment.p_id, sr)
+    return True, msg, log
+
+class InterswitchActionButtonStudent(APABStudent):
+    grok.order(1)
+    grok.context(ICustomStudentOnlinePayment)
+    grok.require('waeup.payStudent')
+    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 InterswitchActionButtonApplicant(APABApplicant):
+    grok.order(1)
+    grok.context(ICustomApplicantOnlinePayment)
+    grok.require('waeup.payApplicant')
+    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 InterswitchRequestWebserviceActionButtonStudent(APABStudent):
+    grok.order(2)
+    grok.context(ICustomStudentOnlinePayment)
+    grok.require('waeup.payStudent')
+    icon = 'actionicon_call.png'
+    text = _('Requery CollegePAY')
+    target = 'request_webservice'
+
+class InterswitchRequestWebserviceActionButtonApplicant(APABApplicant):
+    grok.order(2)
+    grok.context(ICustomApplicantOnlinePayment)
+    grok.require('waeup.payApplicant')
+    icon = 'actionicon_call.png'
+    text = _('Requery CollegePAY')
+    target = 'request_webservice'
+
+class InterswitchPageStudent(KofaPage):
+    """ View which sends a POST request to the Interswitch
+    CollegePAY payment gateway.
+    """
+    grok.context(ICustomStudentOnlinePayment)
+    grok.name('goto_interswitch')
+    grok.template('student_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 = '8302'
+    product_id = PRODUCT_ID
+
+    def update(self):
+        #if self.context.p_state != 'unpaid':
+        if self.context.p_state == 'paid':
+            self.flash(_("Payment ticket can't be re-send to CollegePAY."))
+            self.redirect(self.url(self.context, '@@index'))
+            return
+
+        student = self.student = self.context.student
+        certificate = getattr(student['studycourse'],'certificate',None)
+        self.amount_auth = 100 * self.context.amount_auth
+        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
+        self.category = payment_categories.getTermByToken(
+            self.context.p_category).title
+        tz = getUtility(IKofaUtils).tzinfo
+        self.local_date_time = to_timezone(
+            self.context.creation_date, tz).strftime("%Y-%m-%d %H:%M:%S %Z")
+        self.site_redirect_url = self.url(self.context, 'request_webservice')
+        # Provider data
+        xmldict['detail_ref'] = self.context.p_id
+        xmldict['provider_acct'] = PROVIDER_ACCT
+        xmldict['provider_bank_id'] = PROVIDER_BANK_ID
+        xmldict['provider_item_name'] = PROVIDER_ITEM_NAME
+        xmldict['provider_amt'] = 100 * 500
+        # Institution data
+        studycourse = student['studycourse']
+        if student.current_mode in ('ug_sw','prence',):
+            xmldict['institution_acct'] = "6216801025"
+            xmldict['institution_bank_id'] = '117'
+        elif student.current_mode in ('ug_ft',) and \
+            student['studycourse'].current_verdict == 'O':
+            xmldict['institution_acct'] = "6216801025"
+            xmldict['institution_bank_id'] = '117'
+        elif student.current_mode in ('ug_ft',):
+            xmldict['institution_acct'] = "6216801033"
+            xmldict['institution_bank_id'] = '117'
+        elif student.current_mode in ('pd_ft',):
+            xmldict['institution_acct'] = "6216801025"
+            xmldict['institution_bank_id'] = '117'
+        else:
+            xmldict['kwarapoly_acct'] = "0000000000000"
+            xmldict['institution_bank_id'] = '0'
+        xmldict['kwarapoly_split'] = 100 * 1400
+        xmldict['institution_amt'] = 100 * (
+            self.context.amount_auth - 500 - 150 - 1400)
+        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="KwaraPoly Split" item_amt="%(kwarapoly_split)s" bank_id="117" acct_num="6216801058" />
+<item_detail item_id="3" 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 InterswitchPageApplicant(KofaPage):
+    """ View which sends a POST request to the Interswitch
+    CollegePAY payment gateway.
+    """
+    grok.context(ICustomApplicantOnlinePayment)
+    grok.require('waeup.payApplicant')
+    grok.template('applicant_goto_interswitch')
+    grok.name('goto_interswitch')
+    label = _('Submit data to CollegePAY (Interswitch Payment Gateway)')
+    submit_button = _('Submit')
+    action = POST_ACTION
+    site_name = SITE_NAME
+    currency = CURRENCY
+    pay_item_id = '8303'
+    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
+        if self.context.__parent__.__parent__.expired \
+            and self.context.__parent__.__parent__.strict_deadline:
+            self.flash(_("Payment ticket can't be send to CollegePAY. "
+                         "Application period has expired."))
+            self.redirect(self.url(self.context, '@@index'))
+            return
+        self.applicant = self.context.__parent__
+        self.amount_auth = 100 * self.context.amount_auth
+        xmldict = {}
+        self.category = payment_categories.getTermByToken(
+            self.context.p_category).title
+        tz = getUtility(IKofaUtils).tzinfo
+        self.local_date_time = to_timezone(
+            self.context.creation_date, tz).strftime("%Y-%m-%d %H:%M:%S %Z")
+        self.site_redirect_url = self.url(self.context, 'request_webservice')
+        xmldict['detail_ref'] = self.context.p_id
+        # Provider data
+        xmldict['provider_amt'] = 100 * 500
+        xmldict['provider_acct'] = PROVIDER_ACCT
+        xmldict['provider_bank_id'] = PROVIDER_BANK_ID
+        xmldict['provider_item_name'] = PROVIDER_ITEM_NAME
+        # Institution data
+        xmldict['institution_amt'] = 100 * (self.context.amount_auth - 500 - 150)
+        xmldict['institution_acct'] = '1012445289'
+        xmldict['institution_bank_id'] = '117'
+        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">
+<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 InterswitchPaymentRequestWebservicePageStudent(UtilityView, grok.View):
+    """ Request webservice view for the CollegePAY gateway
+    """
+    grok.context(ICustomStudentOnlinePayment)
+    grok.name('request_webservice')
+    grok.require('waeup.payStudent')
+
+    def update(self):
+        ob_class = self.__implemented__.__name__
+        if self.context.p_state == 'paid':
+            self.flash(_('This ticket has already been paid.'))
+            return
+        student = self.context.student
+        success, msg, log = query_interswitch(self.context)
+        student.writeLogMessage(self, log)
+        if not success:
+            self.flash(msg)
+            return
+        success, msg, log = self.context.doAfterStudentPayment()
+        if log is not None:
+            student.writeLogMessage(self, log)
+        self.flash(msg)
+        return
+
+    def render(self):
+        self.redirect(self.url(self.context, '@@index'))
+        return
+
+class InterswitchPaymentRequestWebservicePageApplicant(UtilityView, grok.View):
+    """ Request webservice view for the CollegePAY gateway
+    """
+    grok.context(ICustomApplicantOnlinePayment)
+    grok.name('request_webservice')
+    grok.require('waeup.payApplicant')
+
+    def update(self):
+        if self.context.p_state == 'paid':
+            self.flash(_('This ticket has already been paid.'))
+            return
+        applicant = self.context.__parent__
+        success, msg, log = query_interswitch(self.context)
+        applicant.writeLogMessage(self, log)
+        if not success:
+            self.flash(msg)
+            return
+        success, msg, log = self.context.doAfterApplicantPayment()
+        if log is not None:
+            applicant.writeLogMessage(self, log)
+        self.flash(msg)
+        return
+
+    def render(self):
+        self.redirect(self.url(self.context, '@@index'))
+        return
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/interswitch/browser_templates/applicant_goto_interswitch.pt
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/interswitch/browser_templates/applicant_goto_interswitch.pt	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/interswitch/browser_templates/applicant_goto_interswitch.pt	(revision 9347)
@@ -0,0 +1,76 @@
+<form i18n:domain="waeup.custom"
+  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/context/amount_auth">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/applicant/applicant_id">CUSTID</span>
+        </td>
+      </tr>
+      <tr>
+        <td i18n:translate="" class="fieldname">
+          Customer Name:</td>
+        <td>
+          <span tal:replace="view/applicant/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/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_auth" />
+  <input type="hidden" name="txn_ref" tal:attributes="value view/context/p_id" />
+  <input type="hidden" name="pay_item_name" tal:attributes="value view/context/p_item" />
+  <input type="hidden" name="cust_id_desc" tal:attributes="value view/category" />
+  <input type="hidden" name="cust_id" tal:attributes="value view/applicant/applicant_id" />
+  <input type="hidden" name="cust_name" tal:attributes="value view/applicant/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.kwarapoly/trunk/src/waeup/kwarapoly/interswitch/browser_templates/student_goto_interswitch.pt
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/interswitch/browser_templates/student_goto_interswitch.pt	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/interswitch/browser_templates/student_goto_interswitch.pt	(revision 9347)
@@ -0,0 +1,76 @@
+<form i18n:domain="waeup.custom"
+  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/context/amount_auth">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/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_auth" />
+  <input type="hidden" name="txn_ref" tal:attributes="value view/context/p_id" />
+  <input type="hidden" name="pay_item_name" tal:attributes="value view/context/p_item" />
+  <input type="hidden" name="cust_id_desc" tal:attributes="value view/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.kwarapoly/trunk/src/waeup/kwarapoly/interswitch/tests.py
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/interswitch/tests.py	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/interswitch/tests.py	(revision 9347)
@@ -0,0 +1,174 @@
+## $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.kofa.applicants.tests.test_browser import ApplicantsFullSetup
+from waeup.kofa.configuration import SessionConfiguration
+from waeup.kwarapoly.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 InterswitchTestsStudents(StudentsFullSetup):
+    """Tests for the Interswitch payment gateway.
+    """
+
+    layer = FunctionalLayer
+
+    def setUp(self):
+        super(InterswitchTestsStudents, 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.assertMatches(
+            '...<span>12495.0</span>...',
+            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="1249500.0" />...',
+            self.browser.contents)
+        self.assertMatches(
+            '...item_name="schoolfee" item_amt="1044500" bank_id="117" acct_num="6216801033"...',
+            self.browser.contents)
+        self.assertMatches(
+            '...item_name="KwaraPoly Split" item_amt="140000" bank_id="117" acct_num="6216801058"...',
+            self.browser.contents)
+        self.assertMatches(
+            '...item_name="BT Education" item_amt="50000" bank_id="31" acct_num="0026781725"...',
+            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)
+        # The payment is now in state failed
+        self.assertMatches('...<span>Failed</span>...',
+                          self.browser.contents)
+
+class InterswitchTestsApplicants(ApplicantsFullSetup):
+    """Tests for the Interswitch payment gateway.
+    """
+
+    layer = FunctionalLayer
+
+    def setUp(self):
+        super(InterswitchTestsApplicants, self).setUp()
+        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
+        self.browser.open(self.manage_path)
+        #IWorkflowState(self.student).setState('started')
+        super(InterswitchTestsApplicants, self).fill_correct_values()
+        self.applicantscontainer.application_fee = 1000.0
+        self.browser.getControl(name="transition").value = ['start']
+        self.browser.getControl("Save").click()
+        self.browser.getControl("Add online").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.assertMatches(
+            '...<span>1000.0</span>...',
+            self.browser.contents)
+        self.payment_url = self.browser.url
+
+
+    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="100000.0" />...',
+            self.browser.contents)
+
+    @external_test
+    def test_webservice(self):
+
+        self.browser.open(self.payment_url + '/request_webservice')
+        self.assertMatches('...Unsuccessful callback...',
+                          self.browser.contents)
+        # The payment is now in state failed
+        self.assertMatches('...<span>Failed</span>...',
+                          self.browser.contents)
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/locales.zcml
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/locales.zcml	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/locales.zcml	(revision 9347)
@@ -0,0 +1,8 @@
+<configure xmlns="http://namespaces.zope.org/zope"
+           xmlns:i18n="http://namespaces.zope.org/i18n">
+
+  <include package="zope.i18n" file="meta.zcml" />
+  <i18n:registerTranslations directory="locales" />
+  <include package="waeup.kofa" file="locales.zcml" />
+
+</configure>
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/locales/de/LC_MESSAGES/waeup.kofa.po
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/locales/de/LC_MESSAGES/waeup.kofa.po	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/locales/de/LC_MESSAGES/waeup.kofa.po	(revision 9347)
@@ -0,0 +1,16 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: Henrik Bettermann <henrik@waeup.org>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=iso-8859-1\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: waeup/kofa/browser/breadcrumbs.py:116
+#: waeup/kofa/browser/viewlets.py:624
+msgid "Academics"
+msgstr "Universität"
+
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/locales/de/LC_MESSAGES/waeup.kwarapoly.po
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/locales/de/LC_MESSAGES/waeup.kwarapoly.po	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/locales/de/LC_MESSAGES/waeup.kwarapoly.po	(revision 9347)
@@ -0,0 +1,167 @@
+# #############################################################################
+#
+# 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 Sep  3 12:52:46 2012\n"
+"PO-Revision-Date: 2012-10-17 17:48+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/kwarapoly/browser/theming.py:30
+#, fuzzy
+msgid "KwaraPoly Theme"
+msgstr "Benutzerdefiniertes Thema 1"
+
+#: waeup/kwarapoly/interfaces.py:35
+msgid "School Fee (ignored)"
+msgstr "Schulgebühr (wird nicht verwendet)"
+
+#: waeup/kwarapoly/interfaces.py:41
+msgid "Application Fee (fallback)"
+msgstr ""
+
+#: waeup/kwarapoly/interfaces.py:47
+#, fuzzy
+msgid "Clearance Fee"
+msgstr "Einschreibungsgebühr"
+
+#: waeup/kwarapoly/interfaces.py:53
+msgid "Booking Fee"
+msgstr "Buchungsgebühr"
+
+#: waeup/kwarapoly/interfaces.py:61
+#, fuzzy
+msgid "Maintenance Fee (ignored)"
+msgstr "Miete"
+
+#: waeup/kwarapoly/interfaces.py:67
+msgid "Gown Fee"
+msgstr "Talar-Gebühr"
+
+#: waeup/kwarapoly/interfaces.py:73
+msgid "Transfer Fee"
+msgstr "Studienfachwechselgebühr"
+
+#: waeup/kwarapoly/interfaces.py:88
+msgid "Academic Session"
+msgstr "Akademisches Jahr"
+
+#. Default: ""
+#: waeup/kwarapoly/interswitch/browser.py:103
+msgid "Unsuccessful callback: ${a}"
+msgstr ""
+
+#: waeup/kwarapoly/interswitch/browser.py:108
+msgid "Callback amount does not match."
+msgstr ""
+
+#: waeup/kwarapoly/interswitch/browser.py:113
+msgid "Callback transaction id does not match."
+msgstr ""
+
+#: waeup/kwarapoly/interswitch/browser.py:119
+msgid "Successful callback received"
+msgstr ""
+
+#: waeup/kwarapoly/interswitch/browser.py:128
+#: waeup/kwarapoly/interswitch/browser.py:142
+msgid "CollegePAY"
+msgstr "CollegePAY"
+
+#: waeup/kwarapoly/interswitch/browser.py:156
+#: waeup/kwarapoly/interswitch/browser.py:164
+#, fuzzy
+msgid "Requery CollegePAY"
+msgstr "CollegePAY"
+
+#: waeup/kwarapoly/interswitch/browser.py:175
+#: waeup/kwarapoly/interswitch/browser.py:254
+msgid "Submit data to CollegePAY (Interswitch Payment Gateway)"
+msgstr "Daten an CollegePAY (Interswitch Payment Gateway) senden"
+
+#: waeup/kwarapoly/interswitch/browser.py:176
+#: waeup/kwarapoly/interswitch/browser.py:255
+msgid "Submit"
+msgstr "Senden"
+
+#: waeup/kwarapoly/interswitch/browser.py:186
+#: waeup/kwarapoly/interswitch/browser.py:264
+msgid "Payment ticket can't be re-send to CollegePAY."
+msgstr "Bewertete Bezahl-Tickets können nicht erneut gesendet werden."
+
+#: waeup/kwarapoly/interswitch/browser.py:269
+#, fuzzy
+msgid "Payment ticket can't be send to CollegePAY. Application period has expired."
+msgstr "Bewertete Bezahl-Tickets können nicht erneut gesendet werden."
+
+#: waeup/kwarapoly/interswitch/browser.py:315
+#: waeup/kwarapoly/interswitch/browser.py:342
+msgid "This ticket has already been paid."
+msgstr ""
+
+#. Default: ""
+#: waeup/kwarapoly/interswitch/browser.py:94
+msgid "Invalid callback: ${a}"
+msgstr ""
+
+#: waeup/kwarapoly/students/interfaces.py:74
+msgid "Payment Level"
+msgstr ""
+
+#: waeup/kwarapoly/students/utils.py:194
+msgid "Amount could not be determined."
+msgstr "Betrag konnte nicht ermittelt werden."
+
+#: waeup/kwarapoly/students/utils.py:201
+msgid "This type of payment has already been made."
+msgstr ""
+
+#: waeup/kwarapoly/students/utils.py:66
+msgid "Session configuration object is not available."
+msgstr "Session-Konfiguration ist nicht verfügbar."
+
+#: waeup/kwarapoly/students/utils.py:80
+#: waeup/kwarapoly/students/utils.py:86
+msgid "Study course data are incomplete."
+msgstr ""
+
+#~ msgid "Postgraduates"
+#~ msgstr "Postgraduierte"
+
+#~ msgid "handles postgraduate applicants"
+#~ msgstr "für Antragsteller von Postgraduierten-Studiengängen"
+
+#~ msgid "Surcharge Portal Provider"
+#~ msgstr "Gebühr Portal-Provider"
+
+#~ msgid "Surcharge Interswitch"
+#~ msgstr "Gebühr Interswitch"
+
+#~ msgid "Surcharge 3"
+#~ msgstr "Gebühr 3"
+
+#~ msgid "Acceptance Fee"
+#~ msgstr "Zulassungsgebühr"
+
+#~ msgid "Request CollegePAY callback"
+#~ msgstr "CollegePAY Rückruf"
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/locales/en/LC_MESSAGES/waeup.fceokene.po
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/locales/en/LC_MESSAGES/waeup.fceokene.po	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/locales/en/LC_MESSAGES/waeup.fceokene.po	(revision 9347)
@@ -0,0 +1,138 @@
+# #############################################################################
+#
+# 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 Sep  3 12:52:46 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/fceokene/browser/theming.py:30
+msgid "FCEOkene Theme"
+msgstr ""
+
+#: waeup/fceokene/interfaces.py:35
+msgid "School Fee (ignored)"
+msgstr ""
+
+#: waeup/fceokene/interfaces.py:41
+msgid "Application Fee (fallback)"
+msgstr ""
+
+#: waeup/fceokene/interfaces.py:47
+msgid "Clearance Fee"
+msgstr ""
+
+#: waeup/fceokene/interfaces.py:53
+msgid "Booking Fee"
+msgstr ""
+
+#: waeup/fceokene/interfaces.py:61
+msgid "Maintenance Fee (ignored)"
+msgstr ""
+
+#: waeup/fceokene/interfaces.py:67
+msgid "Gown Fee"
+msgstr ""
+
+#: waeup/fceokene/interfaces.py:73
+msgid "Transfer Fee"
+msgstr ""
+
+#: waeup/fceokene/interfaces.py:88
+msgid "Academic Session"
+msgstr ""
+
+#. Default: ""
+#: waeup/fceokene/interswitch/browser.py:103
+msgid "Unsuccessful callback: ${a}"
+msgstr ""
+
+#: waeup/fceokene/interswitch/browser.py:108
+msgid "Callback amount does not match."
+msgstr ""
+
+#: waeup/fceokene/interswitch/browser.py:113
+msgid "Callback transaction id does not match."
+msgstr ""
+
+#: waeup/fceokene/interswitch/browser.py:119
+msgid "Successful callback received"
+msgstr ""
+
+#: waeup/fceokene/interswitch/browser.py:128
+#: waeup/fceokene/interswitch/browser.py:142
+msgid "CollegePAY"
+msgstr ""
+
+#: waeup/fceokene/interswitch/browser.py:156
+#: waeup/fceokene/interswitch/browser.py:164
+msgid "Requery CollegePAY"
+msgstr ""
+
+#: waeup/fceokene/interswitch/browser.py:175
+#: waeup/fceokene/interswitch/browser.py:254
+msgid "Submit data to CollegePAY (Interswitch Payment Gateway)"
+msgstr ""
+
+#: waeup/fceokene/interswitch/browser.py:176
+#: waeup/fceokene/interswitch/browser.py:255
+msgid "Submit"
+msgstr ""
+
+#: waeup/fceokene/interswitch/browser.py:186
+#: waeup/fceokene/interswitch/browser.py:264
+msgid "Payment ticket can't be re-send to CollegePAY."
+msgstr ""
+
+#: waeup/fceokene/interswitch/browser.py:269
+msgid ""
+"Payment ticket can't be send to CollegePAY. Application period has expired."
+msgstr ""
+
+#: waeup/fceokene/interswitch/browser.py:315
+#: waeup/fceokene/interswitch/browser.py:342
+msgid "This ticket has already been paid."
+msgstr ""
+
+#. Default: ""
+#: waeup/fceokene/interswitch/browser.py:94
+msgid "Invalid callback: ${a}"
+msgstr ""
+
+#: waeup/fceokene/students/interfaces.py:74
+msgid "Payment Level"
+msgstr ""
+
+#: waeup/fceokene/students/utils.py:194
+msgid "Amount could not be determined."
+msgstr ""
+
+#: waeup/fceokene/students/utils.py:201
+msgid "This type of payment has already been made."
+msgstr ""
+
+#: waeup/fceokene/students/utils.py:66
+msgid "Session configuration object is not available."
+msgstr ""
+
+#: waeup/fceokene/students/utils.py:80 waeup/fceokene/students/utils.py:86
+msgid "Study course data are incomplete."
+msgstr ""
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/locales/en/LC_MESSAGES/waeup.kofa.po
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/locales/en/LC_MESSAGES/waeup.kofa.po	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/locales/en/LC_MESSAGES/waeup.kofa.po	(revision 9347)
@@ -0,0 +1,27 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: \n"
+"POT-Creation-Date: \n"
+"PO-Revision-Date: \n"
+"Last-Translator: Henrik Bettermann <henrik@waeup.org>\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=iso-8859-1\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+msgid "Beds for Pre-Study Students"
+msgstr "Beds for Pre-NCE Students"
+
+msgid "Beds for Returning Students"
+msgstr "Beds for NCE-II Students"
+
+msgid "Beds for Fresh Students"
+msgstr "Beds for NCE-I Students"
+
+msgid "Beds for Final Year Students"
+msgstr "Beds for NCE-III+ Students"
+
+#. Default: "Don't forget to logout or exit your browser when you're done. If you are having trouble logging in, make sure to enable cookies in your web browser."
+msgid "login_trouble1"
+msgstr "<strong>Notice:</strong> All student ids now start with 'K'. If your data have been migrated from the old portal just put 'K' in front of your old id. Example: B123456 becomes KB123456"
+
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/locales/fr/LC_MESSAGES/waeup.fceokene.po
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/locales/fr/LC_MESSAGES/waeup.fceokene.po	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/locales/fr/LC_MESSAGES/waeup.fceokene.po	(revision 9347)
@@ -0,0 +1,138 @@
+# #############################################################################
+#
+# 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 Sep  3 12:52:46 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/fceokene/browser/theming.py:30
+msgid "FCEOkene Theme"
+msgstr ""
+
+#: waeup/fceokene/interfaces.py:35
+msgid "School Fee (ignored)"
+msgstr ""
+
+#: waeup/fceokene/interfaces.py:41
+msgid "Application Fee (fallback)"
+msgstr ""
+
+#: waeup/fceokene/interfaces.py:47
+msgid "Clearance Fee"
+msgstr ""
+
+#: waeup/fceokene/interfaces.py:53
+msgid "Booking Fee"
+msgstr ""
+
+#: waeup/fceokene/interfaces.py:61
+msgid "Maintenance Fee (ignored)"
+msgstr ""
+
+#: waeup/fceokene/interfaces.py:67
+msgid "Gown Fee"
+msgstr ""
+
+#: waeup/fceokene/interfaces.py:73
+msgid "Transfer Fee"
+msgstr ""
+
+#: waeup/fceokene/interfaces.py:88
+msgid "Academic Session"
+msgstr ""
+
+#. Default: ""
+#: waeup/fceokene/interswitch/browser.py:103
+msgid "Unsuccessful callback: ${a}"
+msgstr ""
+
+#: waeup/fceokene/interswitch/browser.py:108
+msgid "Callback amount does not match."
+msgstr ""
+
+#: waeup/fceokene/interswitch/browser.py:113
+msgid "Callback transaction id does not match."
+msgstr ""
+
+#: waeup/fceokene/interswitch/browser.py:119
+msgid "Successful callback received"
+msgstr ""
+
+#: waeup/fceokene/interswitch/browser.py:128
+#: waeup/fceokene/interswitch/browser.py:142
+msgid "CollegePAY"
+msgstr ""
+
+#: waeup/fceokene/interswitch/browser.py:156
+#: waeup/fceokene/interswitch/browser.py:164
+msgid "Requery CollegePAY"
+msgstr ""
+
+#: waeup/fceokene/interswitch/browser.py:175
+#: waeup/fceokene/interswitch/browser.py:254
+msgid "Submit data to CollegePAY (Interswitch Payment Gateway)"
+msgstr ""
+
+#: waeup/fceokene/interswitch/browser.py:176
+#: waeup/fceokene/interswitch/browser.py:255
+msgid "Submit"
+msgstr ""
+
+#: waeup/fceokene/interswitch/browser.py:186
+#: waeup/fceokene/interswitch/browser.py:264
+msgid "Payment ticket can't be re-send to CollegePAY."
+msgstr ""
+
+#: waeup/fceokene/interswitch/browser.py:269
+msgid ""
+"Payment ticket can't be send to CollegePAY. Application period has expired."
+msgstr ""
+
+#: waeup/fceokene/interswitch/browser.py:315
+#: waeup/fceokene/interswitch/browser.py:342
+msgid "This ticket has already been paid."
+msgstr ""
+
+#. Default: ""
+#: waeup/fceokene/interswitch/browser.py:94
+msgid "Invalid callback: ${a}"
+msgstr ""
+
+#: waeup/fceokene/students/interfaces.py:74
+msgid "Payment Level"
+msgstr ""
+
+#: waeup/fceokene/students/utils.py:194
+msgid "Amount could not be determined."
+msgstr ""
+
+#: waeup/fceokene/students/utils.py:201
+msgid "This type of payment has already been made."
+msgstr ""
+
+#: waeup/fceokene/students/utils.py:66
+msgid "Session configuration object is not available."
+msgstr ""
+
+#: waeup/fceokene/students/utils.py:80 waeup/fceokene/students/utils.py:86
+msgid "Study course data are incomplete."
+msgstr ""
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/locales/ha/LC_MESSAGES/waeup.fceokene.po
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/locales/ha/LC_MESSAGES/waeup.fceokene.po	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/locales/ha/LC_MESSAGES/waeup.fceokene.po	(revision 9347)
@@ -0,0 +1,138 @@
+# #############################################################################
+#
+# 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 Sep  3 12:52:46 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/fceokene/browser/theming.py:30
+msgid "FCEOkene Theme"
+msgstr ""
+
+#: waeup/fceokene/interfaces.py:35
+msgid "School Fee (ignored)"
+msgstr ""
+
+#: waeup/fceokene/interfaces.py:41
+msgid "Application Fee (fallback)"
+msgstr ""
+
+#: waeup/fceokene/interfaces.py:47
+msgid "Clearance Fee"
+msgstr ""
+
+#: waeup/fceokene/interfaces.py:53
+msgid "Booking Fee"
+msgstr ""
+
+#: waeup/fceokene/interfaces.py:61
+msgid "Maintenance Fee (ignored)"
+msgstr ""
+
+#: waeup/fceokene/interfaces.py:67
+msgid "Gown Fee"
+msgstr ""
+
+#: waeup/fceokene/interfaces.py:73
+msgid "Transfer Fee"
+msgstr ""
+
+#: waeup/fceokene/interfaces.py:88
+msgid "Academic Session"
+msgstr ""
+
+#. Default: ""
+#: waeup/fceokene/interswitch/browser.py:103
+msgid "Unsuccessful callback: ${a}"
+msgstr ""
+
+#: waeup/fceokene/interswitch/browser.py:108
+msgid "Callback amount does not match."
+msgstr ""
+
+#: waeup/fceokene/interswitch/browser.py:113
+msgid "Callback transaction id does not match."
+msgstr ""
+
+#: waeup/fceokene/interswitch/browser.py:119
+msgid "Successful callback received"
+msgstr ""
+
+#: waeup/fceokene/interswitch/browser.py:128
+#: waeup/fceokene/interswitch/browser.py:142
+msgid "CollegePAY"
+msgstr ""
+
+#: waeup/fceokene/interswitch/browser.py:156
+#: waeup/fceokene/interswitch/browser.py:164
+msgid "Requery CollegePAY"
+msgstr ""
+
+#: waeup/fceokene/interswitch/browser.py:175
+#: waeup/fceokene/interswitch/browser.py:254
+msgid "Submit data to CollegePAY (Interswitch Payment Gateway)"
+msgstr ""
+
+#: waeup/fceokene/interswitch/browser.py:176
+#: waeup/fceokene/interswitch/browser.py:255
+msgid "Submit"
+msgstr ""
+
+#: waeup/fceokene/interswitch/browser.py:186
+#: waeup/fceokene/interswitch/browser.py:264
+msgid "Payment ticket can't be re-send to CollegePAY."
+msgstr ""
+
+#: waeup/fceokene/interswitch/browser.py:269
+msgid ""
+"Payment ticket can't be send to CollegePAY. Application period has expired."
+msgstr ""
+
+#: waeup/fceokene/interswitch/browser.py:315
+#: waeup/fceokene/interswitch/browser.py:342
+msgid "This ticket has already been paid."
+msgstr ""
+
+#. Default: ""
+#: waeup/fceokene/interswitch/browser.py:94
+msgid "Invalid callback: ${a}"
+msgstr ""
+
+#: waeup/fceokene/students/interfaces.py:74
+msgid "Payment Level"
+msgstr ""
+
+#: waeup/fceokene/students/utils.py:194
+msgid "Amount could not be determined."
+msgstr ""
+
+#: waeup/fceokene/students/utils.py:201
+msgid "This type of payment has already been made."
+msgstr ""
+
+#: waeup/fceokene/students/utils.py:66
+msgid "Session configuration object is not available."
+msgstr ""
+
+#: waeup/fceokene/students/utils.py:80 waeup/fceokene/students/utils.py:86
+msgid "Study course data are incomplete."
+msgstr ""
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/locales/waeup.kwarapoly.pot
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/locales/waeup.kwarapoly.pot	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/locales/waeup.kwarapoly.pot	(revision 9347)
@@ -0,0 +1,138 @@
+##############################################################################
+#
+# 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 Sep  3 12:52:46 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/kwarapoly/browser/theming.py:30
+msgid "KwaraPoly Theme"
+msgstr ""
+
+#: waeup/kwarapoly/interfaces.py:35
+msgid "School Fee (ignored)"
+msgstr ""
+
+#: waeup/kwarapoly/interfaces.py:41
+msgid "Application Fee (fallback)"
+msgstr ""
+
+#: waeup/kwarapoly/interfaces.py:47
+msgid "Clearance Fee"
+msgstr ""
+
+#: waeup/kwarapoly/interfaces.py:53
+msgid "Booking Fee"
+msgstr ""
+
+#: waeup/kwarapoly/interfaces.py:61
+msgid "Maintenance Fee (ignored)"
+msgstr ""
+
+#: waeup/kwarapoly/interfaces.py:67
+msgid "Gown Fee"
+msgstr ""
+
+#: waeup/kwarapoly/interfaces.py:73
+msgid "Transfer Fee"
+msgstr ""
+
+#: waeup/kwarapoly/interfaces.py:88
+msgid "Academic Session"
+msgstr ""
+
+#: waeup/kwarapoly/interswitch/browser.py:103
+#. Default: ""
+msgid "Unsuccessful callback: ${a}"
+msgstr ""
+
+#: waeup/kwarapoly/interswitch/browser.py:108
+msgid "Callback amount does not match."
+msgstr ""
+
+#: waeup/kwarapoly/interswitch/browser.py:113
+msgid "Callback transaction id does not match."
+msgstr ""
+
+#: waeup/kwarapoly/interswitch/browser.py:119
+msgid "Successful callback received"
+msgstr ""
+
+#: waeup/kwarapoly/interswitch/browser.py:128
+#: waeup/kwarapoly/interswitch/browser.py:142
+msgid "CollegePAY"
+msgstr ""
+
+#: waeup/kwarapoly/interswitch/browser.py:156
+#: waeup/kwarapoly/interswitch/browser.py:164
+msgid "Requery CollegePAY"
+msgstr ""
+
+#: waeup/kwarapoly/interswitch/browser.py:175
+#: waeup/kwarapoly/interswitch/browser.py:254
+msgid "Submit data to CollegePAY (Interswitch Payment Gateway)"
+msgstr ""
+
+#: waeup/kwarapoly/interswitch/browser.py:176
+#: waeup/kwarapoly/interswitch/browser.py:255
+msgid "Submit"
+msgstr ""
+
+#: waeup/kwarapoly/interswitch/browser.py:186
+#: waeup/kwarapoly/interswitch/browser.py:264
+msgid "Payment ticket can't be re-send to CollegePAY."
+msgstr ""
+
+#: waeup/kwarapoly/interswitch/browser.py:269
+msgid "Payment ticket can't be send to CollegePAY. Application period has expired."
+msgstr ""
+
+#: waeup/kwarapoly/interswitch/browser.py:315
+#: waeup/kwarapoly/interswitch/browser.py:342
+msgid "This ticket has already been paid."
+msgstr ""
+
+#: waeup/kwarapoly/interswitch/browser.py:94
+#. Default: ""
+msgid "Invalid callback: ${a}"
+msgstr ""
+
+#: waeup/kwarapoly/students/interfaces.py:74
+msgid "Payment Level"
+msgstr ""
+
+#: waeup/kwarapoly/students/utils.py:194
+msgid "Amount could not be determined."
+msgstr ""
+
+#: waeup/kwarapoly/students/utils.py:201
+msgid "This type of payment has already been made."
+msgstr ""
+
+#: waeup/kwarapoly/students/utils.py:66
+msgid "Session configuration object is not available."
+msgstr ""
+
+#: waeup/kwarapoly/students/utils.py:80
+#: waeup/kwarapoly/students/utils.py:86
+msgid "Study course data are incomplete."
+msgstr ""
+
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/locales/yo/LC_MESSAGES/waeup.fceokene.po
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/locales/yo/LC_MESSAGES/waeup.fceokene.po	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/locales/yo/LC_MESSAGES/waeup.fceokene.po	(revision 9347)
@@ -0,0 +1,138 @@
+# #############################################################################
+#
+# 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 Sep  3 12:52:46 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/fceokene/browser/theming.py:30
+msgid "FCEOkene Theme"
+msgstr ""
+
+#: waeup/fceokene/interfaces.py:35
+msgid "School Fee (ignored)"
+msgstr ""
+
+#: waeup/fceokene/interfaces.py:41
+msgid "Application Fee (fallback)"
+msgstr ""
+
+#: waeup/fceokene/interfaces.py:47
+msgid "Clearance Fee"
+msgstr ""
+
+#: waeup/fceokene/interfaces.py:53
+msgid "Booking Fee"
+msgstr ""
+
+#: waeup/fceokene/interfaces.py:61
+msgid "Maintenance Fee (ignored)"
+msgstr ""
+
+#: waeup/fceokene/interfaces.py:67
+msgid "Gown Fee"
+msgstr ""
+
+#: waeup/fceokene/interfaces.py:73
+msgid "Transfer Fee"
+msgstr ""
+
+#: waeup/fceokene/interfaces.py:88
+msgid "Academic Session"
+msgstr ""
+
+#. Default: ""
+#: waeup/fceokene/interswitch/browser.py:103
+msgid "Unsuccessful callback: ${a}"
+msgstr ""
+
+#: waeup/fceokene/interswitch/browser.py:108
+msgid "Callback amount does not match."
+msgstr ""
+
+#: waeup/fceokene/interswitch/browser.py:113
+msgid "Callback transaction id does not match."
+msgstr ""
+
+#: waeup/fceokene/interswitch/browser.py:119
+msgid "Successful callback received"
+msgstr ""
+
+#: waeup/fceokene/interswitch/browser.py:128
+#: waeup/fceokene/interswitch/browser.py:142
+msgid "CollegePAY"
+msgstr ""
+
+#: waeup/fceokene/interswitch/browser.py:156
+#: waeup/fceokene/interswitch/browser.py:164
+msgid "Requery CollegePAY"
+msgstr ""
+
+#: waeup/fceokene/interswitch/browser.py:175
+#: waeup/fceokene/interswitch/browser.py:254
+msgid "Submit data to CollegePAY (Interswitch Payment Gateway)"
+msgstr ""
+
+#: waeup/fceokene/interswitch/browser.py:176
+#: waeup/fceokene/interswitch/browser.py:255
+msgid "Submit"
+msgstr ""
+
+#: waeup/fceokene/interswitch/browser.py:186
+#: waeup/fceokene/interswitch/browser.py:264
+msgid "Payment ticket can't be re-send to CollegePAY."
+msgstr ""
+
+#: waeup/fceokene/interswitch/browser.py:269
+msgid ""
+"Payment ticket can't be send to CollegePAY. Application period has expired."
+msgstr ""
+
+#: waeup/fceokene/interswitch/browser.py:315
+#: waeup/fceokene/interswitch/browser.py:342
+msgid "This ticket has already been paid."
+msgstr ""
+
+#. Default: ""
+#: waeup/fceokene/interswitch/browser.py:94
+msgid "Invalid callback: ${a}"
+msgstr ""
+
+#: waeup/fceokene/students/interfaces.py:74
+msgid "Payment Level"
+msgstr ""
+
+#: waeup/fceokene/students/utils.py:194
+msgid "Amount could not be determined."
+msgstr ""
+
+#: waeup/fceokene/students/utils.py:201
+msgid "This type of payment has already been made."
+msgstr ""
+
+#: waeup/fceokene/students/utils.py:66
+msgid "Session configuration object is not available."
+msgstr ""
+
+#: waeup/fceokene/students/utils.py:80 waeup/fceokene/students/utils.py:86
+msgid "Study course data are incomplete."
+msgstr ""
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/mail.zcml
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/mail.zcml	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/mail.zcml	(revision 9347)
@@ -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.kwarapoly/trunk/src/waeup/kwarapoly/meta.zcml
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/meta.zcml	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/meta.zcml	(revision 9347)
@@ -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.kwarapoly/trunk/src/waeup/kwarapoly/payments/__init__.py
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/payments/__init__.py	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/payments/__init__.py	(revision 9347)
@@ -0,0 +1,11 @@
+"""This package contains everything regarding payments.
+"""
+# Make this a package.
+from waeup.kofa.payments.container import PaymentsContainer
+from waeup.kofa.payments.payment import SCPayment, OnlinePayment
+
+__all__ = [
+    'PaymentsContainer',
+    'SCPayment',
+    'OnlinePayment',
+    ]
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/payments/interfaces.py
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/payments/interfaces.py	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/payments/interfaces.py	(revision 9347)
@@ -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
+##
+from zope.interface import Attribute
+from zope import schema
+from kofacustom.nigeria.payments.interfaces import INigeriaOnlinePayment
+from waeup.kwarapoly.interfaces import MessageFactory as _
+
+class ICustomOnlinePayment(INigeriaOnlinePayment):
+    """A payment via payment gateways.
+
+    """
+
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/permissions.py
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/permissions.py	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/permissions.py	(revision 9347)
@@ -0,0 +1,30 @@
+## $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 waeup.kofa.permissions import CCOfficer
+
+
+class CRPUOfficer(CCOfficer):
+    """ Permissions of KwaraPoly staff.
+
+    This is basically a copy of the the PortalManager. We exclude some
+    permissions by commenting them out.
+    """
+
+    grok.name('waeup.CRPUOfficer')
+    grok.title(u'KwaraPoly Officer')
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/__init__.py
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/__init__.py	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/__init__.py	(revision 9347)
@@ -0,0 +1,1 @@
+# Make this a package.
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/batching.py
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/batching.py	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/batching.py	(revision 9347)
@@ -0,0 +1,44 @@
+## $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.kwarapoly.students.interfaces import (
+    ICustomStudent, ICustomStudentUpdateByRegNo,
+    ICustomStudentUpdateByMatricNo,
+    ICustomStudentOnlinePayment
+    )
+from waeup.kofa.students.batching import (
+    StudentProcessor, StudentOnlinePaymentProcessor)
+
+class CustomStudentProcessor(StudentProcessor):
+    """A batch processor for ICustomStudent objects.
+    """
+    iface = ICustomStudent
+    iface_byregnumber = ICustomStudentUpdateByRegNo
+    iface_bymatricnumber = ICustomStudentUpdateByMatricNo
+
+class CustomStudentOnlinePaymentProcessor(StudentOnlinePaymentProcessor):
+    """A batch processor for ICustomStudentOnlinePayment objects.
+    """
+    iface = ICustomStudentOnlinePayment
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/browser.py
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/browser.py	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/browser.py	(revision 9347)
@@ -0,0 +1,95 @@
+## $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.i18n import translate
+from hurry.workflow.interfaces import IWorkflowInfo
+from waeup.kofa.interfaces import ADMITTED
+from waeup.kofa.interfaces import MessageFactory as _
+from waeup.kofa.widgets.datewidget import FriendlyDatetimeDisplayWidget
+from waeup.kofa.students.browser import StartClearancePage, BedTicketAddPage
+from kofacustom.nigeria.students.browser import (
+    NigeriaOnlinePaymentDisplayFormPage,
+    NigeriaOnlinePaymentAddFormPage,
+    NigeriaExportPDFPaymentSlipPage,
+    NigeriaStudentClearanceEditFormPage,
+    )
+
+from waeup.kwarapoly.students.interfaces import ICustomStudentOnlinePayment
+
+class CustomOnlinePaymentDisplayFormPage(NigeriaOnlinePaymentDisplayFormPage):
+    """ Page to view an online payment ticket
+    """
+    grok.context(ICustomStudentOnlinePayment)
+    form_fields = grok.AutoFields(ICustomStudentOnlinePayment)
+    form_fields[
+        'creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
+    form_fields[
+        'payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
+
+class CustomOnlinePaymentAddFormPage(NigeriaOnlinePaymentAddFormPage):
+    """ Page to add an online payment ticket
+    """
+    form_fields = grok.AutoFields(ICustomStudentOnlinePayment).select(
+        'p_category')
+
+class CustomExportPDFPaymentSlipPage(NigeriaExportPDFPaymentSlipPage):
+    """Deliver a PDF slip of the context.
+    """
+    grok.context(ICustomStudentOnlinePayment)
+    form_fields = grok.AutoFields(ICustomStudentOnlinePayment)
+    form_fields['creation_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
+    form_fields['payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
+
+class CustomStartClearancePage(StartClearancePage):
+
+    grok.template('startclearance')
+
+    def update(self, SUBMIT=None):
+        if not self.context.state == ADMITTED:
+            self.flash(_("Wrong state"))
+            self.redirect(self.url(self.context))
+            return
+        if not self.portrait_uploaded:
+            self.flash(_("No portrait uploaded."))
+            self.redirect(self.url(self.context, 'change_portrait'))
+            return
+        if not self.all_required_fields_filled:
+            self.flash(_("Not all required fields filled."))
+            self.redirect(self.url(self.context, 'edit_base'))
+            return
+        if SUBMIT is None:
+            return
+        IWorkflowInfo(self.context).fireTransition('start_clearance')
+        self.flash(_('Clearance process has been started.'))
+        self.redirect(self.url(self.context,'cedit'))
+        return
+
+class CustomStudentClearanceEditFormPage(NigeriaStudentClearanceEditFormPage):
+    """ View to edit student clearance data by student
+    """
+
+    def dataNotComplete(self):
+        return False
+
+class BedTicketAddPage(BedTicketAddPage):
+    """ Page to add an online payment ticket
+    """
+    grok.template('bookbed')
+    buttonname = _('Create bed ticket')
+    notice = ''
+    with_ac = False
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/browser_templates/bookbed.pt
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/browser_templates/bookbed.pt	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/browser_templates/bookbed.pt	(revision 9347)
@@ -0,0 +1,11 @@
+<form method="POST" i18n:domain="waeup.kwarapoly">
+
+  <p i18n:translate="">
+    You data have been approved. You can book a bed by
+    clicking the button below.
+  </p>
+
+  <input type="submit" name="SUBMIT" class="btn primary"
+         tal:attributes="value view/buttonname" />
+
+</form>
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/browser_templates/startclearance.pt
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/browser_templates/startclearance.pt	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/browser_templates/startclearance.pt	(revision 9347)
@@ -0,0 +1,11 @@
+<form method="POST" i18n:domain="waeup.kwarapoly">
+
+  <p i18n:translate="">
+    You data have been approved. You can start clearance now by
+    clicking the button below.
+  </p>
+
+  <input type="submit" name="SUBMIT" class="btn primary"
+         tal:attributes="value view/buttonname" />
+
+</form>
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/interfaces.py
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/interfaces.py	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/interfaces.py	(revision 9347)
@@ -0,0 +1,101 @@
+## $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 kofacustom.nigeria.students.interfaces import (
+    INigeriaStudentBase, INigeriaUGStudentClearance, INigeriaPGStudentClearance,
+    INigeriaStudentPersonal, INigeriaStudentStudyLevel,
+    INigeriaStudentStudyCourse, INigeriaCourseTicket,
+    INigeriaStudentUpdateByRegNo, INigeriaStudentUpdateByMatricNo,
+    )
+from waeup.kwarapoly.payments.interfaces import ICustomOnlinePayment
+from waeup.kwarapoly.interfaces import MessageFactory as _
+
+class ICustomStudentBase(INigeriaStudentBase):
+    """Representation of student base data.
+
+    """
+
+class ICustomStudentPersonal(INigeriaStudentPersonal):
+    """Student personal data.
+
+    """
+
+class ICustomUGStudentClearance(INigeriaUGStudentClearance):
+    """Representation of ug student clearance data.
+
+    """
+
+class ICustomPGStudentClearance(INigeriaPGStudentClearance):
+    """Representation of pg student clearance data.
+
+    """
+
+
+class ICustomStudent(ICustomStudentBase,ICustomUGStudentClearance,
+    ICustomPGStudentClearance,ICustomStudentPersonal):
+    """Representation of a student.
+
+    """
+
+class ICustomStudentStudyCourse(INigeriaStudentStudyCourse):
+    """A container for student study levels.
+
+    """
+
+class ICustomStudentStudyLevel(INigeriaStudentStudyLevel):
+    """A container for course tickets.
+
+    """
+
+class ICustomStudentOnlinePayment(ICustomOnlinePayment):
+    """A student payment via payment gateways.
+
+    This Interface does not inherit from IStudentOnlinePayment.
+    Thus all fields from IStudentOnlinePayment have to be repeated here.
+    """
+
+    p_current = schema.Bool(
+        title = _(u'Current Session Payment'),
+        default = True,
+        required = False,
+        )
+
+    p_level = schema.Int(
+        title = _(u'Payment Level'),
+        required = False,
+        readonly = True,
+        )
+
+ICustomStudentOnlinePayment['p_level'].order = ICustomStudentOnlinePayment[
+    'p_session'].order
+
+class ICustomCourseTicket(INigeriaCourseTicket):
+    """A course ticket.
+
+    """
+
+class ICustomStudentUpdateByRegNo(INigeriaStudentUpdateByRegNo):
+    """Representation of a student. Skip regular reg_number validation.
+
+    """
+
+class ICustomStudentUpdateByMatricNo(INigeriaStudentUpdateByMatricNo):
+    """Representation of a student. Skip regular matric_number validation.
+
+    """
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/payments.py
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/payments.py	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/payments.py	(revision 9347)
@@ -0,0 +1,59 @@
+## $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
+##
+"""
+Student payment components.
+"""
+import grok
+from zope.component.interfaces import IFactory
+from zope.interface import implementedBy
+from waeup.kofa.students.interfaces import IStudentNavigation
+from waeup.kofa.students.payments import (
+    StudentOnlinePayment, StudentOnlinePaymentFactory)
+from waeup.kofa.utils.helpers import attrs_to_fields
+from waeup.kwarapoly.students.interfaces import ICustomStudentOnlinePayment
+
+class CustomStudentOnlinePayment(StudentOnlinePayment):
+    """This is a custom online payment for students.
+
+    Since this class inherits from StudentOnlinePayment, it automatically
+    implements the interfaces of the parent class which means
+    all viewlets and views meant for StudentOnlinePayment
+    can also be accessed in the customized system.
+    """
+    grok.implements(ICustomStudentOnlinePayment, IStudentNavigation)
+    grok.provides(ICustomStudentOnlinePayment)
+
+    def __init__(self):
+        super(CustomStudentOnlinePayment, self).__init__()
+        return
+
+    @property
+    def student(self):
+        return self.__parent__.__parent__
+
+CustomStudentOnlinePayment = attrs_to_fields(CustomStudentOnlinePayment)
+
+class CustomStudentOnlinePaymentFactory(StudentOnlinePaymentFactory):
+    """A factory for student online payments.
+    """
+
+    def __call__(self, *args, **kw):
+        return CustomStudentOnlinePayment()
+
+    def getInterfaces(self):
+        return implementedBy(CustomStudentOnlinePayment)
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/student.py
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/student.py	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/student.py	(revision 9347)
@@ -0,0 +1,51 @@
+## $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 StudentFactory
+from waeup.kofa.students.interfaces import IStudentNavigation
+from kofacustom.nigeria.students.student import NigeriaStudent
+from waeup.kwarapoly.students.interfaces import ICustomStudent
+
+class CustomStudent(NigeriaStudent):
+    """This is a student container for the various objects
+    owned by students.
+    """
+    grok.implements(ICustomStudent, IStudentNavigation)
+    grok.provides(ICustomStudent)
+
+
+# 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.
+CustomStudent = attrs_to_fields(CustomStudent)
+
+class CustomStudentFactory(StudentFactory):
+    """A factory for students.
+    """
+
+    def __call__(self, *args, **kw):
+        return CustomStudent()
+
+    def getInterfaces(self):
+        return implementedBy(CustomStudent)
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/studycourse.py
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/studycourse.py	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/studycourse.py	(revision 9347)
@@ -0,0 +1,72 @@
+## $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
+##
+"""
+Container which holds the data of the student study courses
+and contains the (student) study level objects.
+"""
+import grok
+from zope.interface import implementedBy
+from waeup.kofa.students.studycourse import (
+    StudentStudyCourse, StudentStudyCourseFactory)
+from waeup.kofa.utils.helpers import attrs_to_fields
+from waeup.kofa.students.interfaces import IStudentNavigation
+from waeup.kofa.students.workflow import CLEARED, RETURNING, PAID
+from waeup.kwarapoly.students.interfaces import ICustomStudentStudyCourse
+
+class CustomStudentStudyCourse(StudentStudyCourse):
+    """This is a container for study levels.
+    """
+
+    grok.implements(ICustomStudentStudyCourse, IStudentNavigation)
+    grok.provides(ICustomStudentStudyCourse)
+
+    @property
+    def next_session_allowed(self):
+        state = self.student.state
+        certificate = getattr(self, 'certificate', None)
+        if certificate == None:
+            return False
+        if state == CLEARED:
+            return True
+        cond0 = state == RETURNING
+        cond1 = self.previous_verdict in (
+            'A','B','C','F','J','L','M','N','O','X','Z')
+        cond2 = self.current_level in (0, 100)
+        cond3 = certificate.study_mode.startswith('de') and \
+            self.current_level == 200
+        cond4 = certificate.study_mode.startswith('ph') and \
+            self.current_level == 300
+        if cond0 and (cond1 or cond2 or cond3 or cond4):
+            return True
+        cond5 = self.is_postgrad
+        cond6 = state == PAID
+        if  cond5 and cond6:
+            return True
+        return False
+
+CustomStudentStudyCourse = attrs_to_fields(CustomStudentStudyCourse)
+
+class CustomStudentStudyCourseFactory(StudentStudyCourseFactory):
+    """A factory for student study courses.
+    """
+
+    def __call__(self, *args, **kw):
+        return CustomStudentStudyCourse()
+
+    def getInterfaces(self):
+        return implementedBy(CustomStudentStudyCourse)
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/studylevel.py
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/studylevel.py	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/studylevel.py	(revision 9347)
@@ -0,0 +1,75 @@
+## $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
+##
+"""
+Container which holds the data of a student study level
+and contains the course tickets.
+"""
+import grok
+from zope.component.interfaces import IFactory
+from zope.interface import implementedBy
+from waeup.kofa.utils.helpers import attrs_to_fields
+from waeup.kofa.students.studylevel import (
+    StudentStudyLevel, CourseTicket,
+    CourseTicketFactory, StudentStudyLevelFactory)
+from waeup.kofa.students.interfaces import IStudentNavigation
+from waeup.kwarapoly.students.interfaces import (
+    ICustomStudentStudyLevel, ICustomCourseTicket)
+
+
+class CustomStudentStudyLevel(StudentStudyLevel):
+    """This is a container for course tickets.
+    """
+    grok.implements(ICustomStudentStudyLevel, IStudentNavigation)
+    grok.provides(ICustomStudentStudyLevel)
+
+CustomStudentStudyLevel = attrs_to_fields(CustomStudentStudyLevel)
+
+class CustomStudentStudyLevelFactory(StudentStudyLevelFactory):
+    """A factory for student study levels.
+    """
+
+    def __call__(self, *args, **kw):
+        return CustomStudentStudyLevel()
+
+    def getInterfaces(self):
+        return implementedBy(CustomStudentStudyLevel)
+
+class CustomCourseTicket(CourseTicket):
+    """This is a course ticket which allows the
+    student to attend the course. Lecturers will enter scores and more at
+    the end of the term.
+
+    A course ticket contains a copy of the original course and
+    course referrer data. If the courses and/or their referrers are removed, the
+    corresponding tickets remain unchanged. So we do not need any event
+    triggered actions on course tickets.
+    """
+    grok.implements(ICustomCourseTicket, IStudentNavigation)
+    grok.provides(ICustomCourseTicket)
+
+CustomCourseTicket = attrs_to_fields(CustomCourseTicket)
+
+class CustomCourseTicketFactory(CourseTicketFactory):
+    """A factory for student study levels.
+    """
+
+    def __call__(self, *args, **kw):
+        return CustomCourseTicket()
+
+    def getInterfaces(self):
+        return implementedBy(CustomCourseTicket)
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/tests/__init__.py
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/tests/__init__.py	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/tests/__init__.py	(revision 9347)
@@ -0,0 +1,1 @@
+# Make this a package.
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/tests/sample_student_data.csv
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/tests/sample_student_data.csv	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/tests/sample_student_data.csv	(revision 9347)
@@ -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.kwarapoly/trunk/src/waeup/kwarapoly/students/tests/test_browser.py
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/tests/test_browser.py	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/tests/test_browser.py	(revision 9347)
@@ -0,0 +1,289 @@
+## $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 StringIO import StringIO
+from hurry.workflow.interfaces import IWorkflowState, IWorkflowInfo
+from zope.component.hooks import setSite, clearSite
+from zope.component import getUtility, createObject
+from zope.interface import verify
+from waeup.kofa.app import University
+from waeup.kofa.students.tests.test_browser import (
+    StudentsFullSetup, SAMPLE_IMAGE)
+from waeup.kofa.testing import FunctionalTestCase
+from waeup.kofa.interfaces import (
+    IExtFileStore, IFileStoreNameChooser)
+from waeup.kofa.students.interfaces import IStudentsUtils
+from waeup.kwarapoly.testing import FunctionalLayer
+
+
+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)
+
+
+    def tearDown(self):
+        super(StudentProcessorTest, self).tearDown()
+        shutil.rmtree(self.workdir)
+        shutil.rmtree(self.dc_root)
+        clearSite()
+        return
+
+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'].booking_fee = 150.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)
+        # Managers can open payment slip
+        self.browser.getLink("Download payment slip").click()
+        self.assertEqual(self.browser.headers['Status'], '200 Ok')
+        self.assertEqual(self.browser.headers['Content-Type'], 'application/pdf')
+        # Set ticket paid
+        ticket = self.student['payments'].items()[0][1]
+        ticket.p_state = 'paid'
+        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.assertMatches('...ticket created...',
+                           self.browser.contents)
+        self.browser.open(self.payments_path + '/addop')
+        self.browser.getControl(name="form.p_category").value = ['transfer']
+        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 = ['bed_allocation']
+        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 = ['hostel_maintenance']
+        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 = ['clearance']
+        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 = ['schoolfee']
+        self.browser.getControl("Create ticket").click()
+        self.assertMatches('...ticket created...',
+                           self.browser.contents)
+        # In state returning we can add a new school fee ticket since
+        # p_session and p_level is different
+        IWorkflowState(self.student).setState('returning')
+        self.browser.open(self.payments_path + '/addop')
+        self.browser.getControl(name="form.p_category").value = ['schoolfee']
+        self.browser.getControl("Create ticket").click()
+        self.assertMatches('...ticket created...',
+                           self.browser.contents)
+        # In state admitted school fee can't be determined
+        IWorkflowState(self.student).setState('admitted')
+        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 an error message will
+        # be shown. No other requirement is being checked.
+        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)
+
+    def test_get_returning_data(self):
+        # Student is in level 100, session 2004 with verdict A
+        utils = getUtility(IStudentsUtils)
+        self.assertEqual(utils.getReturningData(self.student),(2005, 200))
+        self.student['studycourse'].current_verdict = 'C'
+        self.assertEqual(utils.getReturningData(self.student),(2005, 110))
+        self.student['studycourse'].current_verdict = 'D'
+        self.assertEqual(utils.getReturningData(self.student),(2005, 100))
+        return
+
+    def test_set_payment_details(self):
+        self.app['configuration']['2004'].gown_fee = 150.0
+        self.app['configuration']['2004'].transfer_fee = 90.0
+        self.app['configuration']['2004'].booking_fee = 150.0
+        self.app['configuration']['2004'].maint_fee = 180.0
+        self.app['configuration']['2004'].clearance_fee = 120.0
+        utils = getUtility(IStudentsUtils)
+
+        error, payment = utils.setPaymentDetails('schoolfee',self.student)
+        self.assertEqual(payment, None)
+        self.assertEqual(error, u'Amount could not be determined.')
+
+        IWorkflowState(self.student).setState('cleared')
+        error, payment = utils.setPaymentDetails('schoolfee',self.student)
+        self.assertEqual(payment.p_level, 100)
+        self.assertEqual(payment.p_session, 2004)
+        self.assertEqual(payment.amount_auth, 12495.0)
+        self.assertEqual(payment.p_item, u'CERT1')
+        self.assertEqual(error, None)
+
+        IWorkflowState(self.student).setState('returning')
+        error, payment = utils.setPaymentDetails('schoolfee',self.student)
+        self.assertEqual(payment.p_level, 200)
+        self.assertEqual(payment.p_session, 2005)
+        self.assertEqual(payment.amount_auth, 11545.0)
+        self.assertEqual(payment.p_item, u'CERT1')
+        self.assertEqual(error, None)
+
+        error, payment = utils.setPaymentDetails('clearance',self.student)
+        self.assertEqual(payment.p_level, 100)
+        self.assertEqual(payment.p_session, 2004)
+        self.assertEqual(payment.amount_auth, 120.0)
+        self.assertEqual(payment.p_item, u'CERT1')
+        self.assertEqual(error, None)
+
+        error, payment = utils.setPaymentDetails('gown',self.student)
+        self.assertEqual(payment.p_level, 100)
+        self.assertEqual(payment.p_session, 2004)
+        self.assertEqual(payment.amount_auth, 150.0)
+        self.assertEqual(payment.p_item, u'')
+        self.assertEqual(error, None)
+
+        error, payment = utils.setPaymentDetails('hostel_maintenance',self.student)
+        self.assertEqual(payment.p_level, 100)
+        self.assertEqual(payment.p_session, 2004)
+        self.assertEqual(payment.amount_auth, 180.0)
+        self.assertEqual(payment.p_item, u'')
+        self.assertEqual(error, None)
+
+        error, payment = utils.setPaymentDetails('bed_allocation',self.student)
+        self.assertEqual(payment.p_level, 100)
+        self.assertEqual(payment.p_session, 2004)
+        self.assertEqual(payment.amount_auth, 150.0)
+        self.assertEqual(payment.p_item, u'')
+        self.assertEqual(error, None)
+
+        error, payment = utils.setPaymentDetails('transfer',self.student)
+        self.assertEqual(payment.p_level, 100)
+        self.assertEqual(payment.p_session, 2004)
+        self.assertEqual(payment.amount_auth, 90.0)
+        self.assertEqual(payment.p_item, u'')
+        self.assertEqual(error, None)
+
+        error, payment = utils.setPaymentDetails('schoolfee',self.student, 2004, 100)
+        self.assertEqual(error, u'Previous session payment not yet implemented.')
+        return
+
+    def test_student_start_clearance(self):
+        self.browser.open(self.login_path)
+        self.browser.getControl(name="form.login").value = self.student_id
+        self.browser.getControl(name="form.password").value = 'spwd'
+        self.browser.getControl("Login").click()
+
+        IWorkflowInfo(self.student).fireTransition('admit')
+        self.browser.open(self.student_path + '/change_portrait')
+        image = open(SAMPLE_IMAGE, 'rb')
+        ctrl = self.browser.getControl(name='passportuploadedit')
+        file_ctrl = ctrl.mech_control
+        file_ctrl.add_file(image, filename='my_photo.jpg')
+        self.browser.getControl(
+            name='upload_passportuploadedit').click()
+        self.browser.open(self.student_path + '/start_clearance')
+        # In Okene the students can just start clearance without entering
+        # an activation code.
+        self.browser.getControl("Start clearance now").click()
+        self.assertMatches('...Clearance process has been started...',
+                           self.browser.contents)
+
+    def test_student_accommodation(self):
+        # Login
+        self.browser.open(self.login_path)
+        self.browser.getControl(name="form.login").value = self.student_id
+        self.browser.getControl(name="form.password").value = 'spwd'
+        self.browser.getControl("Login").click()
+
+        # Students can book accommodation without AC ...
+        self.browser.open(self.acco_path)
+        IWorkflowInfo(self.student).fireTransition('admit')
+        self.browser.getLink("Book accommodation").click()
+        self.assertFalse('Activation Code:' in self.browser.contents)
+        self.browser.getControl("Create bed ticket").click()
+        # Bed is randomly selected but, since there is only
+        # one bed for this student, we know that
+        self.assertMatches('...Hall 1, Block A, Room 101, Bed A...',
+                           self.browser.contents)
+        return
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/tests/test_student.py
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/tests/test_student.py	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/tests/test_student.py	(revision 9347)
@@ -0,0 +1,127 @@
+## $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 students and related.
+"""
+from datetime import tzinfo
+from zope.component.interfaces import IFactory
+from zope.interface import verify
+from waeup.kofa.testing import FunctionalTestCase
+from waeup.kwarapoly.students.student import CustomStudent, CustomStudentFactory
+from waeup.kwarapoly.students.studycourse import (
+    CustomStudentStudyCourse, CustomStudentStudyCourseFactory)
+from waeup.kwarapoly.students.studylevel import (
+    CustomStudentStudyLevel, CustomCourseTicket,
+    CustomStudentStudyLevelFactory,
+    CustomCourseTicketFactory)
+from waeup.kwarapoly.students.interfaces import (
+    ICustomStudent, ICustomStudentStudyCourse,
+    ICustomStudentStudyLevel, ICustomCourseTicket)
+from waeup.kwarapoly.testing import FunctionalLayer
+
+
+class CustomStudentFactoryTest(FunctionalTestCase):
+
+    layer = FunctionalLayer
+
+    def setUp(self):
+        super(CustomStudentFactoryTest, self).setUp()
+        self.factory = CustomStudentFactory()
+
+    def tearDown(self):
+        super(CustomStudentFactoryTest, self).tearDown()
+
+    def test_interfaces(self):
+        verify.verifyClass(IFactory, CustomStudentFactory)
+        verify.verifyObject(IFactory, self.factory)
+
+    def test_factory(self):
+        obj = self.factory()
+        assert isinstance(obj, CustomStudent)
+
+    def test_getInterfaces(self):
+        implemented_by = self.factory.getInterfaces()
+        assert implemented_by.isOrExtends(ICustomStudent)
+
+class CustomStudentStudyCourseFactoryTest(FunctionalTestCase):
+
+    layer = FunctionalLayer
+
+    def setUp(self):
+        super(CustomStudentStudyCourseFactoryTest, self).setUp()
+        self.factory = CustomStudentStudyCourseFactory()
+
+    def tearDown(self):
+        super(CustomStudentStudyCourseFactoryTest, self).tearDown()
+
+    def test_interfaces(self):
+        verify.verifyClass(IFactory, CustomStudentStudyCourseFactory)
+        verify.verifyObject(IFactory, self.factory)
+
+    def test_factory(self):
+        obj = self.factory()
+        assert isinstance(obj, CustomStudentStudyCourse)
+
+    def test_getInterfaces(self):
+        implemented_by = self.factory.getInterfaces()
+        assert implemented_by.isOrExtends(ICustomStudentStudyCourse)
+
+class CustomStudentStudyLevelFactoryTest(FunctionalTestCase):
+
+    layer = FunctionalLayer
+
+    def setUp(self):
+        super(CustomStudentStudyLevelFactoryTest, self).setUp()
+        self.factory = CustomStudentStudyLevelFactory()
+
+    def tearDown(self):
+        super(CustomStudentStudyLevelFactoryTest, self).tearDown()
+
+    def test_interfaces(self):
+        verify.verifyClass(IFactory, CustomStudentStudyLevelFactory)
+        verify.verifyObject(IFactory, self.factory)
+
+    def test_factory(self):
+        obj = self.factory()
+        assert isinstance(obj, CustomStudentStudyLevel)
+
+    def test_getInterfaces(self):
+        implemented_by = self.factory.getInterfaces()
+        assert implemented_by.isOrExtends(ICustomStudentStudyLevel)
+
+class CustomCourseTicketFactoryTest(FunctionalTestCase):
+
+    layer = FunctionalLayer
+
+    def setUp(self):
+        super(CustomCourseTicketFactoryTest, self).setUp()
+        self.factory = CustomCourseTicketFactory()
+
+    def tearDown(self):
+        super(CustomCourseTicketFactoryTest, self).tearDown()
+
+    def test_interfaces(self):
+        verify.verifyClass(IFactory, CustomCourseTicketFactory)
+        verify.verifyObject(IFactory, self.factory)
+
+    def test_factory(self):
+        obj = self.factory()
+        assert isinstance(obj, CustomCourseTicket)
+
+    def test_getInterfaces(self):
+        implemented_by = self.factory.getInterfaces()
+        assert implemented_by.isOrExtends(ICustomCourseTicket)
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/tests/test_utils.py
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/tests/test_utils.py	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/tests/test_utils.py	(revision 9347)
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+from waeup.kofa.testing import FunctionalTestCase
+from zope.component import getUtility
+from waeup.kofa.students.interfaces import IStudentsUtils
+from waeup.kofa.students.tests.test_browser import StudentsFullSetup
+from waeup.kwarapoly.testing import FunctionalLayer
+
+class BedAllocationTests(StudentsFullSetup):
+
+    layer = FunctionalLayer
+
+    def test_getAccommodationDetails(self):
+        students_utils = getUtility(IStudentsUtils)
+        acc_details  = students_utils.getAccommodationDetails(self.student)
+        self.assertEqual(acc_details['bt'], 'regular_male_fr')
+        self.student['studycourse'].current_level = 200
+        acc_details  = students_utils.getAccommodationDetails(self.student)
+        self.assertEqual(acc_details['bt'], 'regular_male_re')
+        self.student['studycourse'].current_level = 300
+        acc_details  = students_utils.getAccommodationDetails(self.student)
+        self.assertEqual(acc_details['bt'], 'regular_male_fi')
+        self.student['studycourse'].current_level = 400
+        acc_details  = students_utils.getAccommodationDetails(self.student)
+        self.assertEqual(acc_details['bt'], 'regular_male_fi')
+        self.student['studycourse'].current_level = 500
+        acc_details  = students_utils.getAccommodationDetails(self.student)
+        self.assertEqual(acc_details['bt'], 'regular_male_fi')
+        return
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/utils.py
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/utils.py	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/students/utils.py	(revision 9347)
@@ -0,0 +1,261 @@
+## $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
+import random
+from time import time
+from zope.component import createObject
+from waeup.kofa.interfaces import CLEARED, RETURNING, PAID
+from kofacustom.nigeria.students.utils import NigeriaStudentsUtils
+from waeup.kofa.accesscodes import create_accesscode
+from waeup.kofa.interfaces import CLEARED, RETURNING
+from waeup.kwarapoly.interfaces import MessageFactory as _
+
+class CustomStudentsUtils(NigeriaStudentsUtils):
+    """A collection of customized methods.
+
+    """
+
+    def selectBed(self, available_beds):
+        """Randomly select a bed from a list of available beds.
+
+        """
+        return random.choice(available_beds)
+
+    def getReturningData(self, student):
+        """ This method defines what happens after school fee payment
+        of returning students depending on the student's senate verdict.
+        """
+        prev_level = student['studycourse'].current_level
+        cur_verdict = student['studycourse'].current_verdict
+        if cur_verdict in ('A','B','L','M','N','Z',):
+            # Successful student
+            new_level = divmod(int(prev_level),100)[0]*100 + 100
+        elif cur_verdict == 'C':
+            # Student on probation
+            new_level = int(prev_level) + 10
+        else:
+            # Student is somehow in an undefined state.
+            # Level has to be set manually.
+            new_level = prev_level
+        new_session = student['studycourse'].current_session + 1
+        return new_session, new_level
+
+    def setPaymentDetails(self, category, student,
+            previous_session=None, previous_level=None):
+        """Create Payment object and set the payment data of a student for
+        the payment category specified.
+
+        """
+        details = {}
+        p_item = u''
+        amount = 0.0
+        error = u''
+        if previous_session:
+            return _('Previous session payment not yet implemented.'), None
+        p_session = student['studycourse'].current_session
+        p_level = student['studycourse'].current_level
+        p_current = True
+        session = str(p_session)
+        try:
+            academic_session = grok.getSite()['configuration'][session]
+        except KeyError:
+            return _(u'Session configuration object is not available.'), None
+        if category == 'transfer':
+            amount = academic_session.transfer_fee
+        elif category == 'gown':
+            amount = academic_session.gown_fee
+        elif category == 'bed_allocation':
+            amount = academic_session.booking_fee
+        elif category == 'hostel_maintenance':
+            amount = academic_session.maint_fee
+        elif category == 'clearance':
+            amount = academic_session.clearance_fee
+            try:
+                p_item = student['studycourse'].certificate.code
+            except (AttributeError, TypeError):
+                return _('Study course data are incomplete.'), None
+        elif category == 'schoolfee':
+            try:
+                certificate = student['studycourse'].certificate
+                p_item = certificate.code
+            except (AttributeError, TypeError):
+                return _('Study course data are incomplete.'), None
+
+            # Very special school fee configuration, should be moved to
+            # a seperate file.
+
+            ARTS = ('CRS','ISS','HIS','MUS','ECO','GEO','POL','SOS','CCA','ECU',
+                    'THA','GED','GSE','PES','SPC','ENG','FRE','ARB','HAU','IGB',
+                    'YOR','NCRS','NISS','NHIS','NMUS','NECO','NGEO','NPOL',
+                    'NCCA','NECU','NTHA','NGED','NGSE','NPES','NSPC','NENG',
+                    'NFRE','NARB','NHAU','NIGB','NYOR','NSOS')
+
+            #PDE Students
+            if student.current_mode == 'pd_ft':
+                amount = 35000
+            elif not student.current_mode.endswith('_sw'):
+                # PRENCE
+                if student.current_level == 10 and student.state == CLEARED:
+                    if student.depcode in ARTS:
+                        amount = 14900
+                    else:
+                        amount = 15400
+                # NCE I fresh
+                elif student.current_level == 100 and student.state == CLEARED:
+                    if student.depcode in ARTS:
+                        amount = 12020
+                    else:
+                        amount = 12495
+                # NCE II
+                elif student.current_level in (100, 110, 120) and \
+                    student.state == RETURNING:
+                    if student.depcode in ARTS:
+                        amount = 11070
+                    else:
+                        amount = 11545
+                # NCE III
+                elif student.current_level in (200, 210, 220):
+                    if student.depcode in ARTS:
+                        amount = 11070
+                    else:
+                        amount = 11545
+                # NCE III repeater
+                elif student.current_level in (300, 310, 320) and \
+                    student.current_verdict == 'O':
+                    if student.depcode in ARTS:
+                        amount = 6535
+                    else:
+                        amount = 6773
+                # NCE III spillover
+                elif student.current_level in (300, 310, 320) and \
+                    student.current_verdict == 'B':
+                    if student.depcode in ARTS:
+                        amount = 9170
+                    else:
+                        amount = 9645
+                # NCE III second spillover
+                elif student.current_level in (400, 410, 420) and \
+                    student.current_verdict == 'B':
+                    if student.depcode in ARTS:
+                        amount = 9170
+                    else:
+                        amount = 9645
+            else:
+                if student.current_level == 100 and student.state == CLEARED:
+                    if student.depcode in ARTS:
+                        amount = 21900
+                    else:
+                        amount = 22400
+                # NCE II
+                elif student.current_level in (100, 110, 120) and \
+                    student.state == RETURNING:
+                    if student.depcode in ARTS:
+                        amount = 18400
+                    else:
+                        amount = 18900
+                # NCE III
+                elif student.current_level in (200, 210, 220):
+                    if student.depcode in ARTS:
+                        amount = 20400
+                    else:
+                        amount = 20900
+                # NCE IV
+                elif student.current_level in (300, 310, 320):
+                    if student.depcode in ARTS:
+                        amount = 18400
+                    else:
+                        amount = 18900
+                # NCE V
+                elif student.current_level in (400, 410, 420):
+                    if student.depcode in ARTS:
+                        amount = 18400
+                    else:
+                        amount = 18900
+                # NCE V spillover
+                elif student.current_level in (500, 510, 520) and \
+                    student.current_verdict == 'B':
+                    if student.depcode in ARTS:
+                        amount = 16900
+                    else:
+                        amount = 17400
+                # NCE V second spillover
+                elif student.current_level in (600, 610, 620) and \
+                    student.current_verdict == 'B':
+                    if student.depcode in ARTS:
+                        amount = 16900
+                    else:
+                        amount = 17400
+            if student.state == RETURNING:
+                p_session, p_level = self.getReturningData(student)
+
+        if amount in (0.0, None):
+            return _(u'Amount could not be determined.'), None
+        for key in student['payments'].keys():
+            ticket = student['payments'][key]
+            if ticket.p_state == 'paid' and\
+               ticket.p_category == category and \
+               ticket.p_item == p_item and \
+               ticket.p_session == p_session:
+                  return _('This type of payment has already been made.'), None
+        payment = createObject(u'waeup.StudentOnlinePayment')
+        timestamp = ("%d" % int(time()*10000))[1:]
+        payment.p_id = "p%s" % timestamp
+        payment.p_category = category
+        payment.p_item = p_item
+        payment.p_session = p_session
+        payment.p_level = p_level
+        payment.p_current = p_current
+        payment.amount_auth = float(amount)
+        return None, payment
+
+    def getAccommodationDetails(self, student):
+        """Determine the accommodation data of a student.
+        """
+        d = {}
+        d['error'] = u''
+        hostels = grok.getSite()['hostels']
+        d['booking_session'] = hostels.accommodation_session
+        d['allowed_states'] = hostels.accommodation_states
+        d['startdate'] = hostels.startdate
+        d['enddate'] = hostels.enddate
+        d['expired'] = hostels.expired
+        # Determine bed type
+        studycourse = student['studycourse']
+        certificate = getattr(studycourse,'certificate',None)
+        current_level = studycourse.current_level
+        if None in (current_level, certificate):
+            return d
+        end_level = certificate.end_level
+        if current_level == 10:
+            bt = 'pr'
+        elif current_level == 100:
+            bt = 'fr'
+        elif current_level >= 300:
+            bt = 'fi'
+        else:
+            bt = 're'
+        if student.sex == 'f':
+            sex = 'female'
+        else:
+            sex = 'male'
+        special_handling = 'regular'
+        d['bt'] = u'%s_%s_%s' % (special_handling,sex,bt)
+        return d
+
+    # KwaraPoly prefix
+    STUDENT_ID_PREFIX = u'K'
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/testing.py
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/testing.py	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/testing.py	(revision 9347)
@@ -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 custom package.
+"""
+import os.path
+import waeup.kwarapoly
+from zope.app.testing.functional import ZCMLLayer
+
+ftesting_zcml = os.path.join(
+    os.path.dirname(waeup.kwarapoly.__file__), 'ftesting.zcml')
+FunctionalLayer = ZCMLLayer(ftesting_zcml, __name__, 'FunctionalLayer',
+                            allow_teardown=True)
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/tests/__init__.py
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/tests/__init__.py	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/tests/__init__.py	(revision 9347)
@@ -0,0 +1,2 @@
+# This is a package
+
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/tests/test_configuration.py
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/tests/test_configuration.py	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/tests/test_configuration.py	(revision 9347)
@@ -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.kwarapoly.configuration import (
+    CustomSessionConfiguration, CustomSessionConfigurationFactory)
+from waeup.kwarapoly.interfaces import(
+    ICustomSessionConfiguration, ICustomSessionConfigurationAdd)
+from waeup.kwarapoly.testing import FunctionalLayer
+
+class ConfigurationTest(ConfigurationTest):
+
+    layer = FunctionalLayer
+
+    def setUp(self):
+        super(ConfigurationTest, self).setUp()
+        self.confcontainer = ConfigurationContainer()
+        self.sessionconf = CustomSessionConfiguration()
+        return
+
+    #def tearDown(self):
+    #    super(ConfigurationTest, self).tearDown()
+    #    return
+
+    def test_interfaces(self):
+        #verify.verifyClass(IConfigurationContainer, ConfigurationContainer)
+        #verify.verifyObject(IConfigurationContainer, self.confcontainer)
+        verify.verifyClass(ICustomSessionConfiguration, CustomSessionConfiguration)
+        verify.verifyObject(ICustomSessionConfiguration, self.sessionconf)
+        verify.verifyClass(ICustomSessionConfigurationAdd, CustomSessionConfiguration)
+        verify.verifyObject(ICustomSessionConfigurationAdd, self.sessionconf)
+
+class SessionConfigurationFactoryTest(SessionConfigurationFactoryTest):
+
+    layer = FunctionalLayer
+
+    def setUp(self):
+        super(SessionConfigurationFactoryTest, self).setUp()
+        self.factory = CustomSessionConfigurationFactory()
+
+    #def tearDown(self):
+    #    super(SessionConfigurationFactoryTest, self).tearDown()
+
+    def test_interfaces(self):
+        verify.verifyClass(IFactory, CustomSessionConfigurationFactory)
+        verify.verifyObject(IFactory, self.factory)
+
+    def test_factory(self):
+        obj = self.factory()
+        assert isinstance(obj, CustomSessionConfiguration)
+
+    def test_getInterfaces(self):
+        implemented_by = self.factory.getInterfaces()
+        assert implemented_by.isOrExtends(ICustomSessionConfiguration)
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/tests/test_datacenter.py
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/tests/test_datacenter.py	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/tests/test_datacenter.py	(revision 9347)
@@ -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.kwarapoly.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.kwarapoly/trunk/src/waeup/kwarapoly/tests/test_smtp.py
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/tests/test_smtp.py	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/tests/test_smtp.py	(revision 9347)
@@ -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.kwarapoly.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.kwarapoly/trunk/src/waeup/kwarapoly/university/__init__.py
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/university/__init__.py	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/university/__init__.py	(revision 9347)
@@ -0,0 +1,1 @@
+# Make this a package.
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/utils/__init__.py
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/utils/__init__.py	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/utils/__init__.py	(revision 9347)
@@ -0,0 +1,1 @@
+# Make this a package.
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/utils/tests.py
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/utils/tests.py	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/utils/tests.py	(revision 9347)
@@ -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.kwarapoly.testing import FunctionalLayer
+
+
+class CustomPackageUtilsTests(FunctionalTestCase):
+
+    layer = FunctionalLayer
+
+    def test_foo(self):
+        '''Test method foo.
+        '''
+        return
Index: /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/utils/utils.py
===================================================================
--- /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/utils/utils.py	(revision 9347)
+++ /main/waeup.kwarapoly/trunk/src/waeup/kwarapoly/utils/utils.py	(revision 9347)
@@ -0,0 +1,27 @@
+## $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.
+"""
+from kofacustom.nigeria.utils.utils import NigeriaKofaUtils
+
+class CustomKofaUtils(NigeriaKofaUtils):
+    """A collection of methods subject to customization.
+    """
+
+    PREFERRED_LANGUAGES_DICT = {
+        }
