Index: /main/kofacustom.unidel/trunk/src/kofacustom/unidel/__init__.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/__init__.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/__init__.py	(revision 16720)
@@ -0,0 +1,1 @@
+# Make this a package.
Index: /main/kofacustom.unidel/trunk/src/kofacustom/unidel/applicants/__init__.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/applicants/__init__.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/applicants/__init__.py	(revision 16720)
@@ -0,0 +1,1 @@
+# Make this a package.
Index: /main/kofacustom.unidel/trunk/src/kofacustom/unidel/applicants/applicant.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/applicants/applicant.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/applicants/applicant.py	(revision 16720)
@@ -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 kofacustom.skeleton.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/kofacustom.unidel/trunk/src/kofacustom/unidel/applicants/batching.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/applicants/batching.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/applicants/batching.py	(revision 16720)
@@ -0,0 +1,36 @@
+## $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
+##
+"""Batch processing for applicants.
+"""
+from kofacustom.nigeria.applicants.batching import NigeriaApplicantProcessor
+from waeup.kofa.applicants.batching import ApplicantOnlinePaymentProcessor
+from kofacustom.skeleton.applicants.interfaces import (
+    ICustomApplicant,
+    ICustomApplicantUpdateByRegNo,
+    ICustomApplicantOnlinePayment
+    )
+
+class CustomApplicantProcessor(NigeriaApplicantProcessor):
+
+    iface = ICustomApplicant
+    iface_byregnumber = ICustomApplicantUpdateByRegNo
+
+class CustomApplicantOnlinePaymentProcessor(ApplicantOnlinePaymentProcessor):
+    """A batch processor for ICustomStudentOnlinePayment objects.
+    """
+    iface = ICustomApplicantOnlinePayment
Index: /main/kofacustom.unidel/trunk/src/kofacustom/unidel/applicants/browser.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/applicants/browser.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/applicants/browser.py	(revision 16720)
@@ -0,0 +1,56 @@
+## $Id$
+##
+## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+"""UI components for basic applicants and related components.
+"""
+import grok
+from waeup.kofa.applicants.browser import (
+    ApplicantRegistrationPage, ApplicantsContainerPage)
+from kofacustom.nigeria.applicants.browser import (
+    NigeriaApplicantDisplayFormPage,
+    NigeriaApplicantManageFormPage,
+    NigeriaApplicantEditFormPage,
+    NigeriaPDFApplicationSlip)
+
+from kofacustom.nigeria.applicants.interfaces import (
+    INigeriaPGApplicant, INigeriaUGApplicant,
+    INigeriaPGApplicantEdit, INigeriaUGApplicantEdit,
+    INigeriaApplicantOnlinePayment,
+    UG_OMIT_DISPLAY_FIELDS,
+    UG_OMIT_PDF_FIELDS,
+    UG_OMIT_MANAGE_FIELDS,
+    UG_OMIT_EDIT_FIELDS,
+    PG_OMIT_DISPLAY_FIELDS,
+    PG_OMIT_PDF_FIELDS,
+    PG_OMIT_MANAGE_FIELDS,
+    PG_OMIT_EDIT_FIELDS,
+    )
+from kofacustom.skeleton.applicants.interfaces import (
+    ICustomPGApplicant, ICustomUGApplicant, ICustomApplicant,
+    ICustomPGApplicantEdit, ICustomUGApplicantEdit,
+    ICustomApplicantOnlinePayment,
+    )
+
+from kofacustom.skeleton.interfaces import MessageFactory as _
+
+class CustomApplicantEditFormPage(NigeriaApplicantEditFormPage):
+    """An applicant-centered edit view for applicant data.
+    """
+
+    def unremovable(self, ticket):
+        return True
+
Index: /main/kofacustom.unidel/trunk/src/kofacustom/unidel/applicants/export.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/applicants/export.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/applicants/export.py	(revision 16720)
@@ -0,0 +1,41 @@
+## $Id$
+##
+## Copyright (C) 2014 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
+##
+"""Exporters for applicant-related stuff.
+"""
+import grok
+from waeup.kofa.applicants.interfaces import IApplicantBaseData
+from waeup.kofa.utils.helpers import iface_names
+from kofacustom.nigeria.applicants.export import NigeriaApplicantExporter
+from kofacustom.nigeria.applicants.interfaces import (
+    INigeriaUGApplicant, INigeriaPGApplicant)
+from kofacustom.skeleton.applicants.interfaces import (
+    ICustomUGApplicant, ICustomPGApplicant)
+
+class CustomApplicantExporter(NigeriaApplicantExporter):
+    """Exporter for Custom Applicants.
+    """
+
+    fields = tuple(sorted(set(
+        iface_names(ICustomUGApplicant) +
+        iface_names(ICustomPGApplicant) +
+        iface_names(INigeriaUGApplicant) +
+        iface_names(INigeriaPGApplicant) +
+        iface_names(IApplicantBaseData)
+        ))) + (
+        'password', 'state', 'history', 'container_code', 'application_number',
+        'display_fullname', 'application_date')
Index: /main/kofacustom.unidel/trunk/src/kofacustom/unidel/applicants/interfaces.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/applicants/interfaces.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/applicants/interfaces.py	(revision 16720)
@@ -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 kofacustom.skeleton.interfaces import MessageFactory as _
+from kofacustom.skeleton.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 applicant 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/kofacustom.unidel/trunk/src/kofacustom/unidel/applicants/payment.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/applicants/payment.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/applicants/payment.py	(revision 16720)
@@ -0,0 +1,59 @@
+## $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 kofacustom.skeleton.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, omit=['display_item'])
+
+# 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/kofacustom.unidel/trunk/src/kofacustom/unidel/applicants/tests/__init__.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/applicants/tests/__init__.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/applicants/tests/__init__.py	(revision 16720)
@@ -0,0 +1,1 @@
+# Make this a package.
Index: /main/kofacustom.unidel/trunk/src/kofacustom/unidel/applicants/tests/test_applicant.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/applicants/tests/test_applicant.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/applicants/tests/test_applicant.py	(revision 16720)
@@ -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 kofacustom.skeleton.applicants.applicant import (
+    CustomApplicant, CustomApplicantFactory,
+    )
+from kofacustom.skeleton.applicants.interfaces import ICustomApplicant
+from kofacustom.skeleton.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/kofacustom.unidel/trunk/src/kofacustom/unidel/applicants/tests/test_browser.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/applicants/tests/test_browser.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/applicants/tests/test_browser.py	(revision 16720)
@@ -0,0 +1,92 @@
+## $Id$
+##
+## Copyright (C) 2013 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
+##
+"""
+Test the applicant-related UI components.
+"""
+import os
+import datetime
+from zope.component import createObject, getUtility
+from zope.catalog.interfaces import ICatalog
+from zope.intid.interfaces import IIntIds
+from hurry.workflow.interfaces import IWorkflowState
+from kofacustom.skeleton.testing import FunctionalLayer
+from waeup.kofa.browser.tests.test_pdf import samples_dir
+from waeup.kofa.applicants.tests.test_browser import ApplicantsFullSetup
+from waeup.kofa.applicants.tests.test_batching import ApplicantImportExportSetup
+from kofacustom.skeleton.applicants.export import CustomApplicantExporter
+from kofacustom.skeleton.applicants.batching import CustomApplicantProcessor
+
+class CustomApplicantUITests(ApplicantsFullSetup):
+    # Tests for uploading/browsing the passport image of appplicants
+
+    layer = FunctionalLayer
+
+class ApplicantExporterTest(ApplicantImportExportSetup):
+
+    layer = FunctionalLayer
+
+    def setUp(self):
+        super(ApplicantExporterTest, self).setUp()
+        self.outfile = os.path.join(self.workdir, 'myoutput.csv')
+        self.cat = getUtility(ICatalog, name='applicants_catalog')
+        self.intids = getUtility(IIntIds)
+        return
+
+    def setup_applicant(self, applicant):
+        # set predictable values for `applicant`
+        applicant.reg_number = u'123456'
+        applicant.applicant_id = u'dp2011_654321'
+        applicant.firstname = u'Anna'
+        applicant.lastname = u'Tester'
+        applicant.middlename = u'M.'
+        applicant.nationality = u'NG'
+        applicant.date_of_birth = datetime.date(1981, 2, 4)
+        applicant.sex = 'f'
+        applicant.email = 'anna@sample.com'
+        applicant.phone = u'+234-123-12345'
+        applicant.course1 = self.certificate
+        applicant.course2 = self.certificate
+        applicant.course_admitted = self.certificate
+        applicant.notice = u'Some notice\nin lines.'
+        applicant.screening_score = 98
+        applicant.screening_venue = u'Exam Room'
+        applicant.screening_date = u'Saturday, 16th June 2012 2:00:00 PM'
+        applicant.password = 'any password'
+        return applicant
+
+class ApplicantsContainerUITests(ApplicantsFullSetup):
+    # Tests for ApplicantsContainer class views and pages
+
+    layer = FunctionalLayer
+
+    def test_application_slip(self):
+        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
+        self.slip_path = self.view_path + '/application_slip.pdf'
+        self.browser.open(self.manage_path)
+        self.assertEqual(self.browser.headers['Status'], '200 Ok')
+        self.fill_correct_values()
+        self.browser.getControl("Save").click()
+        IWorkflowState(self.applicant).setState('submitted')
+        self.browser.open(self.view_path)
+        self.browser.getLink("Download application slip").click()
+        self.assertEqual(self.browser.headers['Status'], '200 Ok')
+        self.assertEqual(self.browser.headers['Content-Type'],
+                         'application/pdf')
+        path = os.path.join(samples_dir(), 'application_slip.pdf')
+        open(path, 'wb').write(self.browser.contents)
+        print "Sample application_slip.pdf written to %s" % path
Index: /main/kofacustom.unidel/trunk/src/kofacustom/unidel/applicants/utils.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/applicants/utils.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/applicants/utils.py	(revision 16720)
@@ -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
+##
+"""General helper functions and utilities for the application section.
+"""
+
+from kofacustom.nigeria.applicants.utils import NigeriaApplicantsUtils
+from waeup.kofa.applicants.workflow import (INITIALIZED,
+    STARTED, PAID, ADMITTED, NOT_ADMITTED, SUBMITTED, CREATED)
+from kofacustom.skeleton.interfaces import MessageFactory as _
+
+
+
+class ApplicantsUtils(NigeriaApplicantsUtils):
+    """A collection of parameters and methods subject to customization.
+    """
Index: /main/kofacustom.unidel/trunk/src/kofacustom/unidel/browser/__init__.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/browser/__init__.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/browser/__init__.py	(revision 16720)
@@ -0,0 +1,1 @@
+# Make this a package.
Index: /main/kofacustom.unidel/trunk/src/kofacustom/unidel/browser/pages.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/browser/pages.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/browser/pages.py	(revision 16720)
@@ -0,0 +1,35 @@
+## $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,
+    LoginPage, CertificatePage, CertificateManageFormPage)
+from waeup.kofa.university.interfaces import ICertificate
+from kofacustom.skeleton.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)
+
Index: /main/kofacustom.unidel/trunk/src/kofacustom/unidel/browser/pdf.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/browser/pdf.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/browser/pdf.py	(revision 16720)
@@ -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/kofacustom.unidel/trunk/src/kofacustom/unidel/browser/tests.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/browser/tests.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/browser/tests.py	(revision 16720)
@@ -0,0 +1,53 @@
+## $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 kofacustom.skeleton.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_dummytest(self):
+        return
+
Index: /main/kofacustom.unidel/trunk/src/kofacustom/unidel/configuration.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/configuration.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/configuration.py	(revision 16720)
@@ -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 kofacustom.skeleton.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/kofacustom.unidel/trunk/src/kofacustom/unidel/configure.zcml
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/configure.zcml	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/configure.zcml	(revision 16720)
@@ -0,0 +1,11 @@
+<configure xmlns="http://namespaces.zope.org/zope"
+           xmlns:grok="http://namespaces.zope.org/grok">
+  <include package="kofacustom.skeleton" file="locales.zcml" />
+
+  <!-- kofacustom.nigeria configures everything we basically need.
+  -->
+  <include package="kofacustom.nigeria" />
+
+  <grok:grok package="." />
+
+</configure>
Index: /main/kofacustom.unidel/trunk/src/kofacustom/unidel/etranzact/__init__.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/etranzact/__init__.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/etranzact/__init__.py	(revision 16720)
@@ -0,0 +1,1 @@
+# Make this a package.
Index: /main/kofacustom.unidel/trunk/src/kofacustom/unidel/etranzact/browser.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/etranzact/browser.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/etranzact/browser.py	(revision 16720)
@@ -0,0 +1,167 @@
+## $Id$
+##
+## Copyright (C) 2017 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 kofacustom.nigeria.etranzact.applicantsbrowser import (
+    EtranzactPageApplicant, EtranzactReceiveResponseApplicant,
+    EtranzactRequestPaymentStatusPageApplicant)
+from kofacustom.nigeria.etranzact.studentsbrowser import (
+    EtranzactPageStudent, EtranzactReceiveResponseStudent,
+    EtranzactRequestPaymentStatusPageStudent,
+    webconnect_module_activated)
+from kofacustom.nigeria.etranzact.payoutletbrowser import (
+    EtranzactEnterPinPageStudent, EtranzactEnterPinPageApplicant,
+    EtranzactQueryHistoryPageStudent, EtranzactQueryHistoryPageApplicant,
+    payoutlet_module_activated)
+from kofacustom.nigeria.etranzact.payoutletwebservice  import NigeriaPaymentDataWebservice
+
+# Temporarily we can use the test portal like in kofacustom.nigeria
+
+from kofacustom.nigeria.etranzact.tests import (
+    HOST, HTTPS, SECRET_KEY, TERMINAL_ID, GATEWAY_AMT)
+
+#HOST =
+#HTTPS =
+#SECRET_KEY =
+#TERMINAL_ID =
+#GATEWAY_AMT =
+LOGO_URL = 'https://iuokada.waeup.org/static_custom/iou_logo.png'
+
+class CustomEtranzactPageApplicant(EtranzactPageApplicant):
+
+    host = HOST
+    https = HTTPS
+    secret_key = SECRET_KEY
+    terminal_id = TERMINAL_ID
+    logo_url = LOGO_URL
+    gateway_amt = GATEWAY_AMT
+
+    def update(self):
+        if not webconnect_module_activated(
+            self.context.__parent__.__parent__.year, self.context):
+            self.flash(_('Forbidden'), type='danger')
+            self.redirect(self.url(self.context, '@@index'))
+            return
+        # Already now it becomes an Etranzact payment. We set the net amount
+        # and add the gateway amount.
+        if not self.context.r_company:
+            self.context.net_amt = self.context.amount_auth
+            self.context.amount_auth += self.gateway_amt
+            self.context.gateway_amt = self.gateway_amt
+            self.context.r_company = u'etranzact'
+        self.amount = "%.1f" % self.context.amount_auth
+        error = self.init_update()
+        if error:
+            self.flash(error, type='danger')
+            self.redirect(self.url(self.context, '@@index'))
+        return
+
+class CustomEtranzactReceiveResponseApplicant(EtranzactReceiveResponseApplicant):
+
+    secret_key = SECRET_KEY
+    terminal_id = TERMINAL_ID
+
+class CustomEtranzactRequestPaymentStatusPageApplicant(
+    EtranzactRequestPaymentStatusPageApplicant):
+
+    host = HOST
+    https = HTTPS
+    secret_key = SECRET_KEY
+    terminal_id = TERMINAL_ID
+    logo_url = LOGO_URL
+
+class CustomEtranzactPageStudent(EtranzactPageStudent):
+
+    host = HOST
+    https = HTTPS
+    secret_key = SECRET_KEY
+    terminal_id = TERMINAL_ID
+    logo_url = LOGO_URL
+    gateway_amt = GATEWAY_AMT
+
+    def update(self):
+        if not webconnect_module_activated(
+            self.context.student.current_session, self.context):
+            self.flash(_('Forbidden'), type='danger')
+            self.redirect(self.url(self.context, '@@index'))
+            return
+        # Already now it becomes an Etranzact payment. We set the net amount
+        # and add the gateway amount.
+        if not self.context.r_company:
+            self.context.net_amt = self.context.amount_auth
+            self.context.amount_auth += self.gateway_amt
+            self.context.gateway_amt = self.gateway_amt
+            self.context.r_company = u'etranzact'
+        self.amount = "%.1f" % self.context.amount_auth
+        error = self.init_update()
+        if error:
+            self.flash(error, type='danger')
+            self.redirect(self.url(self.context, '@@index'))
+            return
+        return
+
+class CustomEtranzactReceiveResponseStudent(EtranzactReceiveResponseStudent):
+
+    secret_key = SECRET_KEY
+    terminal_id = TERMINAL_ID
+
+class CustomEtranzactRequestPaymentStatusPageStudent(
+    EtranzactRequestPaymentStatusPageStudent):
+
+    host = HOST
+    https = HTTPS
+    secret_key = SECRET_KEY
+    terminal_id = TERMINAL_ID
+    logo_url = LOGO_URL
+
+# Payoutlet customizations
+
+class CustomEtranzactEnterPinPageStudent(EtranzactEnterPinPageStudent):
+    """Enter confirmation PIN and submit to `EtranzactQueryHistoryPageStudent`
+    """
+    gateway_amt = GATEWAY_AMT
+
+class CustomEtranzactEnterPinPageApplicant(EtranzactEnterPinPageApplicant):
+    """Enter confirmation PIN and submit to `EtranzactQueryHistoryPageApplicant`
+    """
+    gateway_amt = GATEWAY_AMT
+
+class CustomEtranzactQueryHistoryPageStudent(EtranzactQueryHistoryPageStudent):
+    """ Query history of Etranzact payments
+    """
+    terminal_id = TERMINAL_ID
+    host = HOST
+    https = HTTPS
+
+class CustomEtranzactQueryHistoryPageApplicant(EtranzactQueryHistoryPageApplicant):
+    """ Query history of Etranzact payments
+    """
+    terminal_id = TERMINAL_ID
+    host = HOST
+    https = HTTPS
+
+class CustomPaymentDataWebservice(NigeriaPaymentDataWebservice):
+    """A simple webservice to publish payment and payer details on request from
+    accepted IP addresses without authentication.
+    """
+    #ACCEPTED_IP = ('195.219.3.181', '195.219.3.184')
+    ACCEPTED_IP = None
+
+    CATEGORY_MAPPING = {
+        'SCHOOLFEE': ('schoolfee',),
+         }
+
Index: /main/kofacustom.unidel/trunk/src/kofacustom/unidel/ftesting.zcml
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/ftesting.zcml	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/ftesting.zcml	(revision 16720)
@@ -0,0 +1,67 @@
+<configure
+   xmlns="http://namespaces.zope.org/zope"
+   xmlns:kofa="http://namespaces.waeup.org/kofa"
+   i18n_domain="kofacustom.skeleton"
+   package="kofacustom.skeleton"
+   >
+
+  <include package="kofacustom.skeleton" />
+  <include package="kofacustom.skeleton" 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/kofacustom.unidel/trunk/src/kofacustom/unidel/hostels/__init__.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/hostels/__init__.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/hostels/__init__.py	(revision 16720)
@@ -0,0 +1,11 @@
+"""This package contains everything regarding hostels.
+"""
+# Make this a package.
+from waeup.kofa.hostels.container import HostelsContainer
+from waeup.kofa.hostels.hostel import Hostel, Bed
+
+__all__ = [
+    'HostelsContainer',
+    'Hostel',
+    'Bed',
+    ]
Index: /main/kofacustom.unidel/trunk/src/kofacustom/unidel/hostels/browser.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/hostels/browser.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/hostels/browser.py	(revision 16720)
@@ -0,0 +1,39 @@
+## $Id$
+##
+## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+"""UI components for hostels and related components.
+"""
+import grok
+from waeup.kofa.hostels.browser import (
+    HostelAddFormPage, HostelDisplayFormPage, HostelManageFormPage)
+from kofacustom.skeleton.hostels.interfaces import ICustomHostel
+
+class CustomHostelAddFormPage(HostelAddFormPage):
+    """Add-form to add a hostel.
+    """
+    form_fields = grok.AutoFields(ICustomHostel).omit(
+        'beds_reserved', 'hostel_id')
+
+class CustomHostelDisplayFormPage(HostelDisplayFormPage):
+    """ Page to display hostel data
+    """
+    form_fields = grok.AutoFields(ICustomHostel).omit('beds_reserved')
+
+class CustomHostelManageFormPage(HostelManageFormPage):
+    """ View to edit hostel data
+    """
+    form_fields = grok.AutoFields(ICustomHostel).omit('hostel_id', 'beds_reserved')
Index: /main/kofacustom.unidel/trunk/src/kofacustom/unidel/hostels/hostel.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/hostels/hostel.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/hostels/hostel.py	(revision 16720)
@@ -0,0 +1,61 @@
+## $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
+##
+"""
+These are the custom hostels.
+"""
+import grok
+from zope.component.interfaces import IFactory
+from waeup.kofa.utils.helpers import attrs_to_fields
+from waeup.kofa.hostels.hostel import HostelFactory, BedFactory, Hostel, Bed
+from kofacustom.skeleton.hostels.interfaces import ICustomHostel, ICustomBed
+
+class CustomHostel(Hostel):
+    """This is a hostel.
+    """
+    grok.implements(ICustomHostel)
+    grok.provides(ICustomHostel)
+
+CustomHostel = attrs_to_fields(CustomHostel)
+
+class CustomBed(Bed):
+    """This is a bed.
+    """
+    grok.implements(ICustomBed)
+    grok.provides(ICustomBed)
+
+CustomBed = attrs_to_fields(CustomBed)
+
+class CustomHostelFactory(HostelFactory):
+    """A factory for hostels.
+    """
+
+    def __call__(self, *args, **kw):
+        return CustomHostel()
+
+    def getInterfaces(self):
+        return implementedBy(CustomHostel)
+
+class CustomBedFactory(BedFactory):
+    """A factory for beds.
+    """
+
+    def __call__(self, *args, **kw):
+        return CustomBed()
+
+    def getInterfaces(self):
+        return implementedBy(CustomBed)
Index: /main/kofacustom.unidel/trunk/src/kofacustom/unidel/hostels/interfaces.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/hostels/interfaces.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/hostels/interfaces.py	(revision 16720)
@@ -0,0 +1,95 @@
+## $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  grok import getSite
+from datetime import datetime
+from zope.component import getUtility
+from zope.catalog.interfaces import ICatalog
+from zope.interface import invariant, Invalid, Attribute, Interface
+from zope import schema
+from waeup.kofa.interfaces import SimpleKofaVocabulary
+from waeup.kofa.hostels.interfaces import IHostel, IBed
+
+from kofacustom.skeleton.interfaces import MessageFactory as _
+
+blocks = SimpleKofaVocabulary(
+    (_('Block 1'),'1'),
+    (_('Block 2'),'2'),
+    (_('Block 3'),'3'),
+    (_('Block 4'),'4'),
+    (_('Block 5'),'5'),
+    (_('Block 6'),'6'),
+    (_('Block 7'),'7'),
+    (_('Block 8'),'8'),
+    (_('Block 9'),'9'),
+    (_('Block 10'),'10'),
+    (_('Block 11'),'11'),
+    (_('Block 12'),'12'),
+    (_('Block 13'),'13'),
+    (_('Block 14'),'14'),
+    (_('Block 15'),'15'),
+    (_('Block 16'),'16'),
+    (_('Block 17'),'17'),
+    (_('Block 18'),'18'),
+    (_('Block 19'),'19'),
+    )
+
+class ICustomHostel(IHostel):
+    """Representation of a hostel.
+    """
+
+    floors_per_block = schema.Int(
+        title = _(u'Flats per Block'),
+        required = True,
+        default = 1,
+        )
+
+    rooms_per_floor = schema.Int(
+        title = _(u'Rooms per Flat'),
+        required = True,
+        default = 2,
+        )
+
+    blocks_for_female = schema.List(
+        title = _(u'Blocks for Female Students'),
+        value_type = schema.Choice(
+            vocabulary = blocks
+            ),
+        defaultFactory=list,
+        )
+
+    blocks_for_male = schema.List(
+        title = _(u'Blocks for Male Students'),
+        value_type = schema.Choice(
+            vocabulary = blocks
+            ),
+        defaultFactory=list,
+        )
+
+ICustomHostel['floors_per_block'].order = IHostel[
+    'floors_per_block'].order
+ICustomHostel['rooms_per_floor'].order = IHostel[
+    'rooms_per_floor'].order
+ICustomHostel['blocks_for_female'].order = IHostel[
+    'blocks_for_female'].order
+ICustomHostel['blocks_for_male'].order = IHostel[
+    'blocks_for_male'].order
+
+class ICustomBed(IBed):
+    """Representation of a bed.
+    """
+
Index: /main/kofacustom.unidel/trunk/src/kofacustom/unidel/interfaces.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/interfaces.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/interfaces.py	(revision 16720)
@@ -0,0 +1,107 @@
+## $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('kofacustom.skeleton')
+
+class ICustomSessionConfiguration(ISessionConfiguration):
+    """A session configuration object.
+    """
+
+    # Base fees, do not remove.
+
+    clearance_fee = schema.Float(
+        title = _(u'Acceptance Fee'),
+        default = 0.0,
+        required = False,
+        )
+
+    booking_fee = schema.Float(
+        title = _(u'Bed Booking Fee'),
+        default = 0.0,
+        required = False,
+        )
+
+    maint_fee = schema.Float(
+        title = _(u'Rent (fallback)'),
+        default = 0.0,
+        required = False,
+        )
+
+    late_registration_fee = schema.Float(
+        title = _(u'Late Course Reg. Fee'),
+        default = 0.0,
+        required = False,
+        )
+
+    transcript_fee = schema.Float(
+        title = _(u'Transcript Fee'),
+        default = 0.0,
+        required = False,
+        )
+
+    transfer_fee = schema.Float(
+        title = _(u'Transfer Fee'),
+        default = 0.0,
+        required = False,
+        )
+
+    interswitch_enabled = schema.Bool(
+        title = _(u'Interswitch integration enabled'),
+        default = False,
+        )
+
+    etranzact_webconnect_enabled = schema.Bool(
+        title = _(u'Etranzact Webconnect integration enabled'),
+        default = False,
+        )
+
+    etranzact_payoutlet_enabled = schema.Bool(
+        title = _(u'Etranzact Payoutlet integration enabled'),
+        default = False,
+        )
+
+
+    # Additional fees in custom package add here
+
+
+    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/kofacustom.unidel/trunk/src/kofacustom/unidel/interswitch/__init__.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/interswitch/__init__.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/interswitch/__init__.py	(revision 16720)
@@ -0,0 +1,1 @@
+# Make this a package.
Index: /main/kofacustom.unidel/trunk/src/kofacustom/unidel/interswitch/browser.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/interswitch/browser.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/interswitch/browser.py	(revision 16720)
@@ -0,0 +1,230 @@
+## $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 httplib
+import hashlib
+import grok
+from kofacustom.nigeria.interswitch.browser import (
+    module_activated,
+    InterswitchPaymentRequestWebservicePageApplicant,
+    InterswitchPaymentRequestWebservicePageStudent,
+    InterswitchPaymentVerifyWebservicePageApplicant,
+    InterswitchPaymentVerifyWebservicePageStudent,
+    InterswitchPageStudent, InterswitchPageApplicant,
+    )
+from kofacustom.skeleton.students.interfaces import ICustomStudentOnlinePayment
+from kofacustom.skeleton.applicants.interfaces import ICustomApplicantOnlinePayment
+from kofacustom.skeleton.interfaces import MessageFactory as _
+
+PRODUCT_ID = '' # must be provided by Interswitch
+SITE_NAME = 'skeleton-kofa.waeup.org'
+PROVIDER_ACCT = '00000000'
+PROVIDER_BANK_ID = '00'
+PROVIDER_ITEM_NAME = 'WAeAC'
+INSTITUTION_NAME = 'Skeleton'
+CURRENCY = '566'
+GATEWAY_AMT = 150.0
+MAC = '' # must be provided by Interswitch
+
+#POST_ACTION = 'https://webpay.interswitchng.com/paydirect/pay'
+POST_ACTION = 'https://sandbox.interswitchng.com/webpay/pay'
+#HOST = 'webpay.interswitchng.com'
+HOST = 'sandbox.interswitchng.com'
+#URL = '/paydirect/api/v1/gettransaction.json'
+URL = '/webpay/api/v1/gettransaction.json'
+
+httplib.HTTPSConnection.debuglevel = 0
+HTTPS = True
+
+class CustomInterswitchPageStudent(InterswitchPageStudent):
+    """ View which sends a POST request to the Interswitch
+    CollegePAY payment gateway.
+    """
+    grok.context(ICustomStudentOnlinePayment)
+    action = POST_ACTION
+    site_name = SITE_NAME
+    currency = CURRENCY
+    product_id = PRODUCT_ID
+    mac = MAC
+    gateway_amt = GATEWAY_AMT
+
+    def update(self):
+        if not module_activated(
+            self.context.student.current_session, self.context):
+            self.flash(_('Forbidden'), type='danger')
+            self.redirect(self.url(self.context, '@@index'))
+            return
+        error = self.init_update()
+        if error:
+            self.flash(error, type='danger')
+            self.redirect(self.url(self.context, '@@index'))
+            return
+        # Already now it becomes an Interswitch payment. We set the net amount
+        # and add the gateway amount.
+        if not self.context.r_company:
+            self.context.net_amt = self.context.amount_auth
+            self.context.amount_auth += self.gateway_amt
+            self.context.gateway_amt = self.gateway_amt
+            self.context.r_company = u'interswitch'
+        student = self.student
+        xmldict = self.xmldict
+        # 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
+        # Institution data
+        xmldict['institution_acct'] = '00000000'
+        xmldict['institution_bank_id'] = '00'
+        provider_amt = 0.0
+        self.pay_item_id = '0000' # must be provided by Interswitch
+        xmldict['provider_amt'] = 100 * provider_amt
+        xmldict['institution_item_name'] = self.context.category
+        xmldict['institution_name'] = INSTITUTION_NAME
+        xmldict['institution_amt'] = 100 * self.context.net_amt
+        if not self.context.provider_amt:
+            self.context.provider_amt = provider_amt
+            self.context.amount_auth += provider_amt
+        if provider_amt == 0:
+            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_details>
+</payment_item_detail>""" % xmldict
+        else:
+            xmltext = """<payment_item_detail>
+<item_details detail_ref="%(detail_ref)s" college="%(institution_name)s" department="%(department)s" faculty="%(faculty)s">
+<item_detail item_id="1" item_name="%(institution_item_name)s" item_amt="%(institution_amt)d" bank_id="%(institution_bank_id)s" acct_num="%(institution_acct)s" />
+<item_detail item_id="2" item_name="%(provider_item_name)s" item_amt="%(provider_amt)d" bank_id="%(provider_bank_id)s" acct_num="%(provider_acct)s" />
+</item_details>
+</payment_item_detail>""" % xmldict
+        self.xml_data = """<input type="hidden" name="xml_data" value='%s'  />""" % xmltext
+        self.context.provider_amt = provider_amt
+        self.amount_auth = int(100 * self.context.amount_auth)
+        hashargs = (
+            self.context.p_id +
+            PRODUCT_ID +
+            self.pay_item_id +
+            str(int(self.amount_auth)) +
+            self.site_redirect_url +
+            self.mac)
+        self.hashvalue = hashlib.sha512(hashargs).hexdigest()
+        return
+
+class CustomInterswitchPageApplicant(InterswitchPageApplicant):
+    """ View which sends a POST request to the Interswitch
+    CollegePAY payment gateway.
+    """
+    grok.context(ICustomApplicantOnlinePayment)
+    action = POST_ACTION
+    site_name = SITE_NAME
+    currency = CURRENCY
+    pay_item_id = '0000' # must be provided by Interswitch
+    product_id = PRODUCT_ID
+    mac = MAC
+    gateway_amt = GATEWAY_AMT
+
+    def update(self):
+        if not module_activated(
+            self.context.__parent__.__parent__.year, self.context):
+            self.flash(_('Forbidden'), type='danger')
+            self.redirect(self.url(self.context, '@@index'))
+            return
+        error = self.init_update()
+        if error:
+            self.flash(error, type='danger')
+            self.redirect(self.url(self.context, '@@index'))
+            return
+        # Already now it becomes an Interswitch payment. We set the net amount
+        # and add the gateway amount.
+        if not self.context.r_company:
+            self.context.net_amt = self.context.amount_auth
+            self.context.amount_auth += self.gateway_amt
+            self.context.gateway_amt = self.gateway_amt
+            self.context.r_company = u'interswitch'
+        xmldict = {}
+        provider_amt = 400.0
+        xmldict['institution_acct'] = '00000000000'
+        xmldict['institution_bank_id'] = '00'
+        xmldict['detail_ref'] = self.context.p_id
+        xmldict['provider_amt'] = 100 * provider_amt
+        xmldict['provider_acct'] = PROVIDER_ACCT
+        xmldict['provider_bank_id'] = PROVIDER_BANK_ID
+        xmldict['provider_item_name'] = PROVIDER_ITEM_NAME
+        xmldict['institution_item_name'] = self.context.category
+        xmldict['institution_name'] = INSTITUTION_NAME
+        xmldict['institution_amt'] = 100 * self.context.net_amt
+        if not self.context.provider_amt:
+            self.context.provider_amt = provider_amt
+            self.context.amount_auth += provider_amt
+        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
+        self.amount_auth = int(100 * self.context.amount_auth)
+        hashargs = (
+            self.context.p_id +
+            PRODUCT_ID +
+            self.pay_item_id +
+            str(int(self.amount_auth)) +
+            self.site_redirect_url +
+            self.mac)
+        self.hashvalue = hashlib.sha512(hashargs).hexdigest()
+        return
+
+class CustomInterswitchPaymentRequestWebservicePageStudent(
+    InterswitchPaymentRequestWebservicePageStudent):
+    """Request webservice view for the CollegePAY gateway
+    """
+    grok.context(ICustomStudentOnlinePayment)
+    product_id = PRODUCT_ID
+    gateway_host = HOST
+    gateway_url = URL
+    mac = MAC
+
+class CustomInterswitchPaymentVerifyWebservicePageStudent(
+    InterswitchPaymentVerifyWebservicePageStudent):
+    """Payment verify view for the CollegePAY gateway
+    """
+    grok.context(ICustomStudentOnlinePayment)
+    product_id = PRODUCT_ID
+    gateway_host = HOST
+    gateway_url = URL
+    mac = MAC
+
+class CustomInterswitchPaymentRequestWebservicePageApplicant(
+    InterswitchPaymentRequestWebservicePageApplicant):
+    """Request webservice view for the CollegePAY gateway
+    """
+    grok.context(ICustomApplicantOnlinePayment)
+    product_id = PRODUCT_ID
+    gateway_host = HOST
+    gateway_url = URL
+    mac = MAC
+
+class CustomInterswitchPaymentVerifyWebservicePageApplicant(
+    InterswitchPaymentVerifyWebservicePageApplicant):
+    """Payment verify view for the CollegePAY gateway
+    """
+    grok.context(ICustomApplicantOnlinePayment)
+    product_id = PRODUCT_ID
+    gateway_host = HOST
+    gateway_url = URL
+    mac = MAC
Index: /main/kofacustom.unidel/trunk/src/kofacustom/unidel/interswitch/tests.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/interswitch/tests.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/interswitch/tests.py	(revision 16720)
@@ -0,0 +1,255 @@
+## $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
+from datetime import datetime, date, timedelta
+from zope.component import createObject, getUtility
+from zope.catalog.interfaces import ICatalog
+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 kofacustom.skeleton.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.app['configuration']['2004'].interswitch_enabled = True
+
+    def test_interswitch_form(self):
+        # Manager can access InterswitchForm
+        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
+        self.browser.open(self.payments_path)
+        IWorkflowState(self.student).setState('cleared')
+        self.student.nationality = u'NG'
+        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)
+        self.browser.open(self.payments_path)
+        ctrl = self.browser.getControl(name='val_id')
+        self.value = ctrl.options[0]
+        self.browser.getLink(self.value).click()
+        self.assertMatches('...Amount Authorized...',
+                           self.browser.contents)
+        self.assertTrue(
+            '<span>40000.0</span>' in self.browser.contents)
+        self.payment_url = self.browser.url
+        self.browser.getLink("Pay via Interswitch", index=0).click()
+        self.assertTrue('<input type="hidden" name="pay_item_id" value="0000" />' in
+                           self.browser.contents)
+        self.assertEqual(self.student.current_mode, 'ug_ft')
+        self.assertTrue(
+            '<input type="hidden" name="amount" value="4015000" />' in
+            self.browser.contents)
+        self.assertTrue(
+            'item_name="School Fee" item_amt="4000000" bank_id="00" acct_num="00000000"' in
+            self.browser.contents)
+
+    def test_interswitch_form_ticket_expired(self):
+        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
+        acc_payment = createObject('waeup.StudentOnlinePayment')
+        acc_payment.p_state = 'unpaid'
+        acc_payment.p_category = 'clearance'
+        acc_payment.p_id = 'xyz'
+        acc_payment.pay_item_id = '123'
+        acc_payment.amount_auth = 1000.0
+        self.student['payments']['xyz'] = acc_payment
+        self.browser.open(self.payments_path + '/xyz')
+        self.browser.getLink("Pay via Interswitch", index=0).click()
+        self.assertMatches('...<input type="hidden" name="pay_item_id" value="0000" />...',
+                           self.browser.contents)
+        self.assertMatches('...Total Amount Authorized:...',
+                           self.browser.contents)
+        self.assertEqual(self.student.current_mode, 'ug_ft')
+        self.assertMatches(
+            '...<input type="hidden" name="amount" value="115000" />...',
+            self.browser.contents)
+        delta = timedelta(days=8)
+        acc_payment.creation_date -= delta
+        self.browser.open(self.payments_path + '/xyz')
+        self.browser.getLink("Pay via Interswitch", index=0).click()
+        self.assertMatches(
+            '...This payment ticket is too old. Please create a new ticket...',
+            self.browser.contents)
+        delta = timedelta(days=2)
+        acc_payment.creation_date += delta
+        self.browser.open(self.payments_path + '/xyz')
+        self.browser.getLink("Pay via Interswitch", index=0).click()
+        self.assertMatches('...Total Amount Authorized:...',
+                           self.browser.contents)
+
+    @external_test
+    def test_webservice(self):
+        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
+        self.browser.open(self.payments_path)
+        IWorkflowState(self.student).setState('cleared')
+        self.student.nationality = u'NG'
+        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)
+        ctrl = self.browser.getControl(name='val_id')
+        self.value = ctrl.options[0]
+        self.browser.getLink(self.value).click()
+        self.payment_url = self.browser.url
+        # First we have open InterswitchPageStudent to set provider_amt
+        # and gateway_amt
+        self.browser.open(self.payment_url + '/goto_interswitch')
+        # Now we can call the webservice
+        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)
+        # ... and the catalog has been updated
+        cat = getUtility(ICatalog, name='payments_catalog')
+        results = list(
+            cat.searchResults(p_state=('failed', 'failed')))
+        self.assertEqual(len(results), 1)
+        self.assertEqual(results[0].p_state, 'failed')
+
+        # Let's replace the p_id with a valid p_id of the Uniben
+        # live system. This is definitely not an appropriate
+        # solution for testing, but we have no choice since
+        # Interswitch doesn't provide any interface
+        # for testing.
+        payment = self.student['payments'][self.value]
+        payment.p_id = 'p3547789850240'
+        self.browser.open(self.payment_url + '/request_webservice')
+        self.assertMatches('...Callback amount does not match...',
+                          self.browser.contents)
+        # The payment is now in state failed ...
+        self.assertMatches('...<span>Failed</span>...',
+                          self.browser.contents)
+        # Let's replace the amount autorized with the amount of the
+        # live system payment
+        payment.amount_auth = payment.r_amount_approved
+        self.browser.open(self.payment_url + '/request_webservice')
+        self.assertMatches('...Successful payment...',
+                          self.browser.contents)
+        # The payment is now in state paid ...
+        self.assertMatches('...<span>Paid</span>...',
+                          self.browser.contents)
+        # ... and the catalog has been updated
+        cat = getUtility(ICatalog, name='payments_catalog')
+        results = list(
+            cat.searchResults(p_state=('paid', 'paid')))
+        self.assertEqual(len(results), 1)
+        self.assertEqual(results[0].p_state, 'paid')
+        # Approval is logged in students.log ...
+        logfile = os.path.join(
+            self.app['datacenter'].storage, 'logs', 'students.log')
+        logcontent = open(logfile).read()
+        self.assertTrue(
+            'zope.mgr - '
+            'kofacustom.skeleton.interswitch.browser.CustomInterswitchPaymentRequestWebservicePageStudent - '
+            'X1000000 - successful schoolfee payment: p3547789850240\n'
+            in logcontent)
+        # ... and in payments.log
+        logfile = os.path.join(
+            self.app['datacenter'].storage, 'logs', 'payments.log')
+        logcontent = open(logfile).read()
+        self.assertTrue(
+            '"zope.mgr",X1000000,p3547789850240,schoolfee,'
+            '12000.0,00,0.0,150.0,0.0,,,\n'
+            in logcontent)
+
+
+class InterswitchTestsApplicants(ApplicantsFullSetup):
+    """Tests for the Interswitch payment gateway.
+    """
+
+    layer = FunctionalLayer
+
+    def setUp(self):
+        super(InterswitchTestsApplicants, self).setUp()
+        configuration = SessionConfiguration()
+        configuration.academic_session = datetime.now().year - 2
+        self.app['configuration'].addSessionConfiguration(configuration)
+        self.configuration = configuration
+        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="form.nationality").value = ['NG']
+        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("Pay via Interswitch", index=0).click()
+        self.assertMatches('...Total Amount Authorized:...',
+                           self.browser.contents)
+        self.assertMatches(
+            '...<input type="hidden" name="amount" value="100000" />...',
+            self.browser.contents)
+        delta = timedelta(days=8)
+        self.applicant.values()[0].creation_date -= delta
+        self.browser.open(self.payment_url)
+        self.browser.getLink("Pay via Interswitch", index=0).click()
+        self.assertMatches(
+            '...This payment ticket is too old. Please create a new ticket...',
+            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/kofacustom.unidel/trunk/src/kofacustom/unidel/locales.zcml
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/locales.zcml	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/locales.zcml	(revision 16720)
@@ -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/kofacustom.unidel/trunk/src/kofacustom/unidel/locales/en/LC_MESSAGES/waeup.kofa.po
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/locales/en/LC_MESSAGES/waeup.kofa.po	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/locales/en/LC_MESSAGES/waeup.kofa.po	(revision 16720)
@@ -0,0 +1,14 @@
+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 "Academics"
+msgstr "Institutions"
+
Index: /main/kofacustom.unidel/trunk/src/kofacustom/unidel/mail.zcml
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/mail.zcml	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/mail.zcml	(revision 16720)
@@ -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/kofacustom.unidel/trunk/src/kofacustom/unidel/meta.zcml
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/meta.zcml	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/meta.zcml	(revision 16720)
@@ -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/kofacustom.unidel/trunk/src/kofacustom/unidel/payments/__init__.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/payments/__init__.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/payments/__init__.py	(revision 16720)
@@ -0,0 +1,3 @@
+"""This package contains everything regarding payments.
+"""
+# Make this a package.
Index: /main/kofacustom.unidel/trunk/src/kofacustom/unidel/payments/interfaces.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/payments/interfaces.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/payments/interfaces.py	(revision 16720)
@@ -0,0 +1,26 @@
+## $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 kofacustom.skeleton.interfaces import MessageFactory as _
+
+class ICustomOnlinePayment(INigeriaOnlinePayment):
+    """A payment via payment gateways.
+
+    """
Index: /main/kofacustom.unidel/trunk/src/kofacustom/unidel/permissions.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/permissions.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/permissions.py	(revision 16720)
@@ -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 Computer Center members.
+
+    This is basically a copy of the the PortalManager. We exclude some
+    permissions by commenting them out.
+    """
+
+    grok.name('waeup.CCOfficer')
+    grok.title(u'CC Officer')
Index: /main/kofacustom.unidel/trunk/src/kofacustom/unidel/remita/__init__.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/remita/__init__.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/remita/__init__.py	(revision 16720)
@@ -0,0 +1,1 @@
+# Make this a package.
Index: /main/kofacustom.unidel/trunk/src/kofacustom/unidel/remita/browser.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/remita/browser.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/remita/browser.py	(revision 16720)
@@ -0,0 +1,178 @@
+## $Id$
+##
+## Copyright (C) 2017 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 kofacustom.nigeria.remita.studentsbrowser import (
+    module_activated,
+    RemitaRequestPaymentStatusPageStudent,
+    RemitaVerifyPaymentStatusPageStudent,
+    RemitaPageStudent)
+from kofacustom.nigeria.remita.applicantsbrowser import (
+    RemitaRequestPaymentStatusPageApplicant,
+    RemitaVerifyPaymentStatusPageApplicant,
+    RemitaPageApplicant)
+
+# Temporarily we can use the test portal like in kofacustom.nigeria
+
+from kofacustom.nigeria.remita.tests import (
+    MERCHANTID, HOST, HTTPS, API_KEY, SERVICETYPEID)
+
+#MERCHANTID = ''
+#HOST = 'login.remita.net'
+#HTTPS = True
+#API_KEY = ''
+#SERVICETYPEID = ''
+
+class CustomRemitaRequestPaymentStatusPageStudent(
+    RemitaRequestPaymentStatusPageStudent):
+    """ Request webservice view for the Remita gateway.
+    """
+
+    merchantId = MERCHANTID
+    host = HOST
+    https = HTTPS
+    api_key = API_KEY
+
+class CustomRemitaVerifyPaymentStatusPageStudent(
+    RemitaVerifyPaymentStatusPageStudent):
+    """ Request webservice view for the Remita gateway.
+    """
+
+    merchantId = MERCHANTID
+    host = HOST
+    https = HTTPS
+    api_key = API_KEY
+
+class CustomRemitaPageStudent(RemitaPageStudent):
+    """ View which sends a POST request to the Remita payment gateway.
+    """
+
+    merchantId = MERCHANTID
+    host = HOST
+    https = HTTPS
+    api_key = API_KEY
+
+    init_url = '/remita/ecomm/split/init.reg'
+
+    @property
+    def serviceTypeId(self):
+        return SERVICETYPEID
+
+    @property
+    def lineitems(self):
+        ba1 = self.context.amount_auth / 2
+        ba2 = self.context.amount_auth / 2
+        lineitems = (
+                      {"lineItemsId":"itemid1","beneficiaryName":"Klaus Mueller",
+                      "beneficiaryAccount":"6020067886","bankCode":"011",
+                      "beneficiaryAmount":str(ba1),"deductFeeFrom":"1"},
+                      {"lineItemsId":"itemid2","beneficiaryName":"Werner Rumm",
+                      "beneficiaryAccount":"0360883515","bankCode":"050",
+                      "beneficiaryAmount":str(ba2),"deductFeeFrom":"0"}
+                    )
+        return lineitems
+
+    def update(self):
+        if not module_activated(
+            self.context.student.current_session, self.context):
+            self.flash(_('Forbidden'), type='danger')
+            self.redirect(self.url(self.context, '@@index'))
+            return
+        self.orderId = self.context.p_id
+        error = self.init_update()
+        if error:
+            self.flash(error, type='danger')
+            self.redirect(self.url(self.context, '@@index'))
+            return
+        # Already now it becomes a Remita payment. We set the net amount
+        # and add the gateway amount.
+        if not self.context.r_company:
+            self.context.net_amt = self.context.amount_auth
+            self.context.amount_auth += self.gateway_amt
+            self.context.gateway_amt = self.gateway_amt
+            self.context.r_company = u'remita'
+        self.amount_auth = int(100 * self.context.amount_auth)
+        return
+
+class CustomRemitaRequestPaymentStatusPageApplicant(
+    RemitaRequestPaymentStatusPageApplicant):
+    """ Request webservice view for the Remita gateway.
+    """
+
+    merchantId = MERCHANTID
+    host = HOST
+    https = HTTPS
+    api_key = API_KEY
+
+class CustomRemitaVerifyPaymentStatusPageApplicant(
+    RemitaVerifyPaymentStatusPageApplicant):
+    """ Request webservice view for the Remita gateway.
+    """
+
+    merchantId = MERCHANTID
+    host = HOST
+    https = HTTPS
+    api_key = API_KEY
+
+class CustomRemitaPageApplicant(RemitaPageApplicant):
+    """ View which sends a POST request to the Remita payment gateway.
+    """
+
+    merchantId = MERCHANTID
+    host = HOST
+    https = HTTPS
+    api_key = API_KEY
+
+    init_url = '/remita/ecomm/split/init.reg'
+
+    @property
+    def serviceTypeId(self):
+        return SERVICETYPEID
+
+    @property
+    def lineitems(self):
+        lineitems = (
+                      {"lineItemsId":"itemid1","beneficiaryName":"Klaus Mueller",
+                      "beneficiaryAccount":"6020067886","bankCode":"011",
+                      "beneficiaryAmount":self.context.amount_auth/2,"deductFeeFrom":"1"},
+                      {"lineItemsId":"itemid2","beneficiaryName":"Werner Rumm",
+                      "beneficiaryAccount":"0360883515","bankCode":"050",
+                      "beneficiaryAmount":self.context.amount_auth/2,"deductFeeFrom":"0"}
+                    )
+        return lineitems
+
+    def update(self):
+        if not module_activated(
+            self.context.__parent__.__parent__.year, self.context):
+            self.flash(_('Forbidden'), type='danger')
+            self.redirect(self.url(self.context, '@@index'))
+            return
+        self.orderId = self.context.p_id
+        error = self.init_update()
+        if error:
+            self.flash(error, type='danger')
+            self.redirect(self.url(self.context, '@@index'))
+            return
+        # Already now it becomes a Remita payment. We set the net amount
+        # and add the gateway amount.
+        if not self.context.r_company:
+            self.context.net_amt = self.context.amount_auth
+            self.context.amount_auth += self.gateway_amt
+            self.context.gateway_amt = self.gateway_amt
+            self.context.r_company = u'remita'
+        self.amount_auth = int(100 * self.context.amount_auth)
+        return
Index: /main/kofacustom.unidel/trunk/src/kofacustom/unidel/remita/webservices.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/remita/webservices.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/remita/webservices.py	(revision 16720)
@@ -0,0 +1,39 @@
+## $Id$
+##
+## Copyright (C) 2017 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 kofacustom.nigeria.remita.webservices import PaymentNotificationListenerWebservice
+from kofacustom.skeleton.remita.browser import MERCHANTID, HOST, HTTPS, API_KEY
+
+class CustomPaymentNotificationListenerWebservice(
+    PaymentNotificationListenerWebservice):
+    """A webservice to receive payment notifications from
+    accepted IP addresses without authentication which trigger
+    a query of a Remita transaction status.
+    """
+
+    ACCEPTED_IP = ('127.0.0.1', '209.235.215.199', '162.13.211.91',
+                   '162.13.211.92', '83.138.167.2', '197.253.4.114')
+
+    merchantId = MERCHANTID
+    host = HOST
+    https = HTTPS
+    api_key = API_KEY
+
+    def update(self, P_ID=None):
+        self.output = 'Webservice disabled'
+        return
Index: /main/kofacustom.unidel/trunk/src/kofacustom/unidel/students/__init__.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/students/__init__.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/students/__init__.py	(revision 16720)
@@ -0,0 +1,1 @@
+# Make this a package.
Index: /main/kofacustom.unidel/trunk/src/kofacustom/unidel/students/batching.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/students/batching.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/students/batching.py	(revision 16720)
@@ -0,0 +1,39 @@
+## $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.
+"""
+from kofacustom.nigeria.students.batching import NigeriaStudentProcessor
+from waeup.kofa.students.batching import StudentOnlinePaymentProcessor
+from kofacustom.skeleton.students.interfaces import (
+    ICustomStudent,
+    ICustomStudentUpdateByRegNo,
+    ICustomStudentUpdateByMatricNo,
+    ICustomStudentOnlinePayment
+    )
+
+class CustomStudentProcessor(NigeriaStudentProcessor):
+    """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/kofacustom.unidel/trunk/src/kofacustom/unidel/students/browser.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/students/browser.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/students/browser.py	(revision 16720)
@@ -0,0 +1,56 @@
+## $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 zope.schema.interfaces import ConstraintNotSatisfied
+from zope.component import getUtility
+from zope.security import checkPermission
+from hurry.workflow.interfaces import IWorkflowInfo
+from waeup.kofa.interfaces import REQUESTED, IExtFileStore, IKofaUtils
+from waeup.kofa.widgets.datewidget import FriendlyDatetimeDisplayWidget
+from waeup.kofa.browser.layout import KofaEditFormPage
+from waeup.kofa.browser.layout import action, jsaction
+from waeup.kofa.students.browser import (
+    StudyLevelEditFormPage, StudyLevelDisplayFormPage,
+    StudentBasePDFFormPage, ExportPDFCourseRegistrationSlip,
+    CourseTicketDisplayFormPage, StudentTriggerTransitionFormPage,
+    PaymentsManageFormPage, msave, emit_lock_message)
+from waeup.kofa.students.interfaces import IStudentsUtils, ICourseTicket
+from waeup.kofa.students.workflow import FORBIDDEN_POSTGRAD_TRANS
+from kofacustom.nigeria.students.browser import (
+    NigeriaOnlinePaymentDisplayFormPage,
+    NigeriaStudentBaseManageFormPage,
+    NigeriaStudentClearanceEditFormPage,
+    NigeriaOnlinePaymentAddFormPage,
+    NigeriaExportPDFPaymentSlip,
+    NigeriaExportPDFClearanceSlip,
+    )
+
+from kofacustom.skeleton.students.interfaces import (
+    ICustomStudentOnlinePayment, ICustomStudentStudyCourse,
+    ICustomStudentStudyLevel)
+from kofacustom.skeleton.interfaces import MessageFactory as _
+
+class CustomPaymentsManageFormPage(PaymentsManageFormPage):
+    """ Page to manage the student payments. This manage form page is for
+    both students and students officers. SKELETON does not allow students
+    to remove any payment ticket.
+    """
+    @property
+    def manage_payments_allowed(self):
+        return checkPermission('waeup.manageStudent', self.context)
Index: /main/kofacustom.unidel/trunk/src/kofacustom/unidel/students/export.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/students/export.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/students/export.py	(revision 16720)
@@ -0,0 +1,75 @@
+## $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
+##
+"""Exporters for student related stuff.
+"""
+from kofacustom.skeleton.students.interfaces import (
+    ICustomStudent,
+    ICustomStudentStudyCourse,
+    ICustomStudentStudyLevel,
+    ICustomCourseTicket,
+    ICustomStudentOnlinePayment)
+from kofacustom.nigeria.students.export import (
+    NigeriaStudentExporter,
+    NigeriaStudentStudyCourseExporter,
+    NigeriaStudentStudyLevelExporter,
+    NigeriaCourseTicketExporter,
+    NigeriaStudentPaymentExporter)
+from waeup.kofa.utils.helpers import iface_names
+
+class CustomStudentExporter(NigeriaStudentExporter):
+    """Exporter for Students.
+    """
+
+    fields = tuple(sorted(iface_names(
+        ICustomStudent, omit=['loggerInfo']))) + (
+        'password', 'state', 'history', 'certcode', 'is_postgrad',
+        'current_level', 'current_session')
+
+class CustomStudentStudyCourseExporter(NigeriaStudentStudyCourseExporter):
+    """Exporter for StudentStudyCourses.
+    """
+
+    fields = tuple(
+        sorted(iface_names(ICustomStudentStudyCourse))) + ('student_id',)
+
+class CustomStudentStudyLevelExporter(NigeriaStudentStudyLevelExporter):
+    """Exporter for StudentStudyLevels.
+    """
+    #: Fieldnames considered by this exporter
+    fields = tuple(sorted(iface_names(
+        ICustomStudentStudyLevel))) + (
+        'student_id', 'number_of_tickets','certcode')
+
+class CustomCourseTicketExporter(NigeriaCourseTicketExporter):
+    """Exporter for CourseTickets.
+    """
+
+    fields = tuple(sorted(iface_names(ICustomCourseTicket) +
+        ['level', 'code', 'level_session'])) + ('student_id',
+        'certcode', 'display_fullname')
+
+class CustomStudentPaymentExporter(NigeriaStudentPaymentExporter):
+    """Exporter for OnlinePayment instances.
+    """
+
+    fields = tuple(
+        sorted(iface_names(
+            ICustomStudentOnlinePayment, exclude_attribs=False,
+            omit=['display_item']))) + (
+            'student_id','state','current_session')
+
Index: /main/kofacustom.unidel/trunk/src/kofacustom/unidel/students/interfaces.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/students/interfaces.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/students/interfaces.py	(revision 16720)
@@ -0,0 +1,102 @@
+## $Id$
+##
+## Copyright (C) 2012 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+
+from zope import schema
+from waeup.kofa.students.vocabularies import StudyLevelSource
+from kofacustom.nigeria.students.interfaces import (
+    INigeriaStudentBase, INigeriaUGStudentClearance, INigeriaPGStudentClearance,
+    INigeriaStudentPersonal, INigeriaStudentStudyLevel,
+    INigeriaStudentStudyCourse, INigeriaCourseTicket,
+    INigeriaStudentUpdateByRegNo, INigeriaStudentUpdateByMatricNo,
+    )
+from kofacustom.skeleton.payments.interfaces import ICustomOnlinePayment
+from kofacustom.skeleton.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.Choice(
+        title = _(u'Payment Level'),
+        source = StudyLevelSource(),
+        required = False,
+        )
+
+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/kofacustom.unidel/trunk/src/kofacustom/unidel/students/payments.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/students/payments.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/students/payments.py	(revision 16720)
@@ -0,0 +1,60 @@
+## $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.utils.helpers import attrs_to_fields
+from kofacustom.nigeria.students.payments import (
+    NigeriaStudentOnlinePayment, NigeriaStudentOnlinePaymentFactory)
+from kofacustom.skeleton.students.interfaces import ICustomStudentOnlinePayment
+
+class CustomStudentOnlinePayment(NigeriaStudentOnlinePayment):
+    """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, omit=['display_item'])
+
+class CustomStudentOnlinePaymentFactory(NigeriaStudentOnlinePaymentFactory):
+    """A factory for student online payments.
+    """
+
+    def __call__(self, *args, **kw):
+        return CustomStudentOnlinePayment()
+
+    def getInterfaces(self):
+        return implementedBy(CustomStudentOnlinePayment)
Index: /main/kofacustom.unidel/trunk/src/kofacustom/unidel/students/student.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/students/student.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/students/student.py	(revision 16720)
@@ -0,0 +1,60 @@
+## $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 waeup.kofa.utils.helpers import get_current_principal
+from kofacustom.nigeria.students.student import NigeriaStudent
+from kofacustom.skeleton.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)
+
+    @property
+    def transcript_enabled(self):
+        user = get_current_principal()
+        if user.id in ('admin', 'isouaba', 'niyi', 'delejason'):
+            return True
+        return False
+
+
+# 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/kofacustom.unidel/trunk/src/kofacustom/unidel/students/studycourse.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/students/studycourse.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/students/studycourse.py	(revision 16720)
@@ -0,0 +1,47 @@
+## $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 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 kofacustom.nigeria.students.studycourse import NigeriaStudentStudyCourse
+from kofacustom.skeleton.students.interfaces import ICustomStudentStudyCourse
+
+class CustomStudentStudyCourse(NigeriaStudentStudyCourse):
+    """This is a container for study levels.
+    """
+    grok.implements(ICustomStudentStudyCourse, IStudentNavigation)
+    grok.provides(ICustomStudentStudyCourse)
+
+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/kofacustom.unidel/trunk/src/kofacustom/unidel/students/studylevel.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/students/studylevel.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/students/studylevel.py	(revision 16720)
@@ -0,0 +1,77 @@
+## $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 kofacustom.skeleton.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, omit=['total_credits', 'gpa'])
+
+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/kofacustom.unidel/trunk/src/kofacustom/unidel/students/tests/__init__.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/students/tests/__init__.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/students/tests/__init__.py	(revision 16720)
@@ -0,0 +1,1 @@
+# Make this a package.
Index: /main/kofacustom.unidel/trunk/src/kofacustom/unidel/students/tests/test_browser.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/students/tests/test_browser.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/students/tests/test_browser.py	(revision 16720)
@@ -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
+##
+import os
+import shutil
+import tempfile
+from StringIO import StringIO
+from hurry.workflow.interfaces import IWorkflowState, IWorkflowInfo
+from zope.securitypolicy.interfaces import IPrincipalRoleManager
+from zope.component.hooks import setSite, clearSite
+from zope.component import getUtility, createObject
+from zope.interface import verify
+from zope.event import notify
+from waeup.kofa.authentication import LocalRoleSetEvent
+from waeup.kofa.app import University
+from waeup.kofa.students.tests.test_browser import StudentsFullSetup
+from waeup.kofa.students.accommodation import BedTicket
+from waeup.kofa.testing import FunctionalTestCase
+from waeup.kofa.interfaces import (
+    IExtFileStore, IFileStoreNameChooser)
+from waeup.kofa.students.interfaces import IStudentsUtils
+from kofacustom.skeleton.testing import FunctionalLayer
+
+
+class StudentsContainerUITests(StudentsFullSetup):
+    # Tests for StudentsContainer class views and pages
+
+    layer = FunctionalLayer
+
+    def test_dummytest(self):
+        return
Index: /main/kofacustom.unidel/trunk/src/kofacustom/unidel/students/tests/test_export.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/students/tests/test_export.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/students/tests/test_export.py	(revision 16720)
@@ -0,0 +1,116 @@
+import datetime
+from zope.component import queryUtility
+from zope.interface.verify import verifyObject, verifyClass
+from waeup.kofa.interfaces import ICSVExporter
+from waeup.kofa.schoolgrades import ResultEntry
+from waeup.kofa.students.tests.test_batching import (
+    StudentImportExportSetup, curr_year)
+from kofacustom.skeleton.utils.utils import CustomKofaUtils
+from kofacustom.skeleton.students.export import (
+    CustomStudentExporter, CustomStudentPaymentExporter)
+from kofacustom.skeleton.testing import FunctionalLayer
+
+
+class CustomStudentExporterTest(StudentImportExportSetup):
+
+    layer = FunctionalLayer
+
+    def setUp(self):
+        super(CustomStudentExporterTest, self).setUp()
+        self.setup_for_export()
+        result_entry = ResultEntry(
+            CustomKofaUtils.EXAM_SUBJECTS_DICT.keys()[0],
+            CustomKofaUtils.EXAM_GRADES[0][0]
+            )
+        self.student.alr_results = [
+            result_entry]
+        self.student.fst_sit_results = [
+            result_entry]
+        self.student.scd_sit_results = [
+            result_entry]
+        return
+
+    def test_ifaces(self):
+        # make sure we fullfill interface contracts
+        obj = CustomStudentExporter()
+        verifyObject(ICSVExporter, obj)
+        verifyClass(ICSVExporter, CustomStudentExporter)
+        return
+
+
+    def test_export_all(self):
+        # we can really export students
+        # set values we can expect in export file
+        self.setup_student(self.student)
+        exporter = CustomStudentExporter()
+        exporter.export_all(self.app, self.outfile)
+        result = open(self.outfile, 'rb').read()
+        self.assertMatches(
+            'adm_code,alr_date,alr_fname,alr_no,alr_results,'
+            'clr_code,date_of_birth,def_adm,disabled,email,emp2_end,'
+            'emp2_position,emp2_reason,emp2_start,emp_end,emp_position,'
+            'emp_reason,emp_start,employer,employer2,'
+            'financial_clearance_date,financially_cleared_by,'
+            'firstname,flash_notice,former_matric,'
+            'fst_sit_date,fst_sit_fname,fst_sit_no,fst_sit_results,'
+            'fst_sit_type,hq2_degree,hq2_disc,hq2_matric_no,hq2_school,'
+            'hq2_session,hq2_type,hq_degree,hq_disc,hq_fname,hq_matric_no,'
+            'hq_school,hq_session,hq_type,is_staff,lastname,lga,'
+            'marit_stat,matric_number,middlename,nationality,'
+            'next_kin_address,next_kin_name,next_kin_phone,next_kin_relation,'
+            'nysc_lga,nysc_location,nysc_year,officer_comment,parents_email,'
+            'perm_address,personal_updated,phone,'
+            'physical_clearance_date,provisionally_cleared,reg_number,'
+            'religion,scd_sit_date,scd_sit_fname,scd_sit_no,'
+            'scd_sit_results,scd_sit_type,sex,student_id,'
+            'suspended,suspended_comment,password,state,history,certcode,is_postgrad,'
+            'current_level,current_session\r\nmy adm code,,,,'
+            '"[(\'printing_craft_practice\', \'A1\')]",my clr code,1981-02-04#,,,'
+            'anna@sample.com,,,,,,,,,,,,,Anna,,,,,,"[(\'printing_craft_practice\', \'A1\')]"'
+            ',,,,,,,,,,,,,,,,Tester,,,234,M.,NG,,,,,,,,,,'
+            '"Studentroad 21\nLagos 123456\n",,+234-123-12345#,,,123,,,,,'
+            '"[(\'printing_craft_practice\', \'A1\')]",,f,A111111,0,,,created,'
+            '[u\'2012-11-06 13:16:41 WAT - Record created by system\'],'
+            'CERT1,0,200,2012\r\n',
+            result
+            )
+        return
+
+class CustomStudentPaymentExporterTest(StudentImportExportSetup):
+
+    layer = FunctionalLayer
+
+    def setUp(self):
+        super(CustomStudentPaymentExporterTest, self).setUp()
+        self.setup_for_export()
+        return
+
+    def test_ifaces(self):
+        # make sure we fullfill interface contracts
+        obj = CustomStudentPaymentExporter()
+        verifyObject(ICSVExporter, obj)
+        verifyClass(ICSVExporter, CustomStudentPaymentExporter)
+        return
+
+    def test_export_all(self):
+        # we can really export students
+        # set values we can expect in export file
+        self.setup_student(self.student)
+        self.student['payments']['my-payment'].r_company = 'interswatch'
+        self.student['payments']['my-payment'].r_card_num = '789'
+        exporter = CustomStudentPaymentExporter()
+        exporter.export_all(self.app, self.outfile)
+        result = open(self.outfile, 'rb').read()
+        self.assertMatches(
+            'ac,amount_auth,creation_date,gateway_amt,net_amt,'
+            'p_category,p_combi,p_current,'
+            'p_id,p_item,p_level,p_option,p_session,p_state,payment_date,provider_amt,'
+            'r_amount_approved,r_card_num,r_code,r_company,r_desc,'
+            'r_pay_reference,thirdparty_amt,student_id,state,'
+            'current_session\r\n'
+            '666,12.12,%s-04-01 13:12:01#,,,schoolfee,[],1,my-id,p-item,'
+            '100,,%s,paid,%s-04-01 14:12:01#,,12.12,'
+            '789,r-code,interswatch,,,,A111111,created,2012\r\n'
+            % (curr_year-6, curr_year-6, curr_year-6), result
+            )
+        return
Index: /main/kofacustom.unidel/trunk/src/kofacustom/unidel/students/tests/test_student.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/students/tests/test_student.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/students/tests/test_student.py	(revision 16720)
@@ -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 kofacustom.skeleton.students.student import CustomStudent, CustomStudentFactory
+from kofacustom.skeleton.students.studycourse import (
+    CustomStudentStudyCourse, CustomStudentStudyCourseFactory)
+from kofacustom.skeleton.students.studylevel import (
+    CustomStudentStudyLevel, CustomCourseTicket,
+    CustomStudentStudyLevelFactory,
+    CustomCourseTicketFactory)
+from kofacustom.skeleton.students.interfaces import (
+    ICustomStudent, ICustomStudentStudyCourse,
+    ICustomStudentStudyLevel, ICustomCourseTicket)
+from kofacustom.skeleton.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/kofacustom.unidel/trunk/src/kofacustom/unidel/students/utils.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/students/utils.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/students/utils.py	(revision 16720)
@@ -0,0 +1,31 @@
+## $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 time import time
+from zope.component import createObject, getUtility
+from waeup.kofa.interfaces import (IKofaUtils,
+    CLEARED, RETURNING, PAID, REGISTERED, VALIDATED)
+from kofacustom.nigeria.students.utils import NigeriaStudentsUtils
+from kofacustom.skeleton.interfaces import MessageFactory as _
+
+class CustomStudentsUtils(NigeriaStudentsUtils):
+    """A collection of customized methods.
+
+    """
+
+    # refix
+    STUDENT_ID_PREFIX = u'X'
Index: /main/kofacustom.unidel/trunk/src/kofacustom/unidel/students/viewlets.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/students/viewlets.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/students/viewlets.py	(revision 16720)
@@ -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.interfaces import REQUESTED
+from waeup.kofa.browser.viewlets import ManageActionButton
+from kofacustom.skeleton.students.interfaces import (
+    ICustomStudentStudyCourse, ICustomStudentStudyLevel)
+from waeup.kofa.students.fileviewlets import (
+    StudentFileDisplay, StudentFileUpload, StudentImage)
+from waeup.kofa.students.browser import (
+    ExportPDFClearanceSlip, StudyCourseDisplayFormPage,
+    StudyLevelDisplayFormPage)
+
+from kofacustom.nigeria.interfaces import MessageFactory as _
Index: /main/kofacustom.unidel/trunk/src/kofacustom/unidel/testing.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/testing.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/testing.py	(revision 16720)
@@ -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 kofacustom.skeleton
+from zope.app.testing.functional import ZCMLLayer
+
+ftesting_zcml = os.path.join(
+    os.path.dirname(kofacustom.skeleton.__file__), 'ftesting.zcml')
+FunctionalLayer = ZCMLLayer(ftesting_zcml, __name__, 'FunctionalLayer',
+                            allow_teardown=True)
Index: /main/kofacustom.unidel/trunk/src/kofacustom/unidel/tests/__init__.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/tests/__init__.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/tests/__init__.py	(revision 16720)
@@ -0,0 +1,2 @@
+# This is a package
+
Index: /main/kofacustom.unidel/trunk/src/kofacustom/unidel/tests/test_configuration.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/tests/test_configuration.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/tests/test_configuration.py	(revision 16720)
@@ -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 kofacustom.skeleton.configuration import (
+    CustomSessionConfiguration, CustomSessionConfigurationFactory)
+from kofacustom.skeleton.interfaces import(
+    ICustomSessionConfiguration, ICustomSessionConfigurationAdd)
+from kofacustom.skeleton.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/kofacustom.unidel/trunk/src/kofacustom/unidel/tests/test_datacenter.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/tests/test_datacenter.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/tests/test_datacenter.py	(revision 16720)
@@ -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 kofacustom.skeleton.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/kofacustom.unidel/trunk/src/kofacustom/unidel/tests/test_smtp.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/tests/test_smtp.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/tests/test_smtp.py	(revision 16720)
@@ -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 kofacustom.skeleton.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/kofacustom.unidel/trunk/src/kofacustom/unidel/university/__init__.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/university/__init__.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/university/__init__.py	(revision 16720)
@@ -0,0 +1,1 @@
+# Make this a package.
Index: /main/kofacustom.unidel/trunk/src/kofacustom/unidel/utils/__init__.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/utils/__init__.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/utils/__init__.py	(revision 16720)
@@ -0,0 +1,1 @@
+# Make this a package.
Index: /main/kofacustom.unidel/trunk/src/kofacustom/unidel/utils/tests.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/utils/tests.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/utils/tests.py	(revision 16720)
@@ -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 kofacustom.skeleton.testing import FunctionalLayer
+
+
+class CustomPackageUtilsTests(FunctionalTestCase):
+
+    layer = FunctionalLayer
+
+    def test_foo(self):
+        '''Test method foo.
+        '''
+        return
Index: /main/kofacustom.unidel/trunk/src/kofacustom/unidel/utils/utils.py
===================================================================
--- /main/kofacustom.unidel/trunk/src/kofacustom/unidel/utils/utils.py	(revision 16720)
+++ /main/kofacustom.unidel/trunk/src/kofacustom/unidel/utils/utils.py	(revision 16720)
@@ -0,0 +1,25 @@
+## $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.
+    """
+
