Index: main/waeup.uniben/trunk/src/waeup/uniben/applicants/applicant.py
===================================================================
--- main/waeup.uniben/trunk/src/waeup/uniben/applicants/applicant.py	(revision 8927)
+++ main/waeup.uniben/trunk/src/waeup/uniben/applicants/applicant.py	(revision 8928)
@@ -22,10 +22,10 @@
 from waeup.kofa.utils.helpers import attrs_to_fields
 from waeup.uniben.applicants.interfaces import(
-    ICustomApplicant, IUGApplicantEdit, IPGApplicantEdit, IPUTMEApplicantEdit)
+    ICustomApplicant, ICustomUGApplicantEdit, ICustomPGApplicantEdit, IPUTMEApplicantEdit)
 
 class CustomApplicant(Applicant):
 
-    grok.implements(ICustomApplicant, IUGApplicantEdit,
-        IPGApplicantEdit, IPUTMEApplicantEdit)
+    grok.implements(ICustomApplicant, ICustomUGApplicantEdit,
+        ICustomPGApplicantEdit, IPUTMEApplicantEdit)
     grok.provides(ICustomApplicant)
 
Index: main/waeup.uniben/trunk/src/waeup/uniben/applicants/browser.py
===================================================================
--- main/waeup.uniben/trunk/src/waeup/uniben/applicants/browser.py	(revision 8927)
+++ main/waeup.uniben/trunk/src/waeup/uniben/applicants/browser.py	(revision 8928)
@@ -36,5 +36,5 @@
 from waeup.kofa.applicants.pdf import PDFApplicationSlip
 from waeup.uniben.applicants.interfaces import (
-    IPGApplicant, IUGApplicant, IPGApplicantEdit, IUGApplicantEdit,
+    ICustomPGApplicant, ICustomUGApplicant, ICustomPGApplicantEdit, ICustomUGApplicantEdit,
     ICustomApplicantOnlinePayment, IPUTMEApplicantEdit,
     UG_OMIT_DISPLAY_FIELDS, PG_OMIT_DISPLAY_FIELDS,
@@ -81,9 +81,9 @@
         target = getattr(self.context.__parent__, 'prefix', None)
         if target is not None and target.startswith('pg'):
-            form_fields = grok.AutoFields(IPGApplicant)
+            form_fields = grok.AutoFields(ICustomPGApplicant)
             for field in PG_OMIT_PDF_FIELDS:
                 form_fields = form_fields.omit(field)
         else:
-            form_fields = grok.AutoFields(IUGApplicant)
+            form_fields = grok.AutoFields(ICustomUGApplicant)
             if self._reduced_slip():
                 for field in UG_OMIT_RESULT_SLIP_FIELDS:
@@ -104,9 +104,9 @@
         target = getattr(self.context.__parent__, 'prefix', None)
         if target is not None and target.startswith('pg'):
-            form_fields = grok.AutoFields(IPGApplicant)
+            form_fields = grok.AutoFields(ICustomPGApplicant)
             for field in PG_OMIT_DISPLAY_FIELDS:
                 form_fields = form_fields.omit(field)
         else:
-            form_fields = grok.AutoFields(IUGApplicant)
+            form_fields = grok.AutoFields(ICustomUGApplicant)
             for field in UG_OMIT_DISPLAY_FIELDS:
                 form_fields = form_fields.omit(field)
@@ -121,9 +121,9 @@
         target = getattr(self.context.__parent__, 'prefix', None)
         if target is not None and target.startswith('pg'):
-            form_fields = grok.AutoFields(IPGApplicant)
+            form_fields = grok.AutoFields(ICustomPGApplicant)
             for field in PG_OMIT_MANAGE_FIELDS:
                 form_fields = form_fields.omit(field)
         else:
-            form_fields = grok.AutoFields(IUGApplicant)
+            form_fields = grok.AutoFields(ICustomUGApplicant)
             for field in UG_OMIT_MANAGE_FIELDS:
                 form_fields = form_fields.omit(field)
@@ -140,5 +140,5 @@
         target = getattr(self.context.__parent__, 'prefix', None)
         if target is not None and target.startswith('pg'):
-            form_fields = grok.AutoFields(IPGApplicantEdit)
+            form_fields = grok.AutoFields(ICustomPGApplicantEdit)
             for field in PG_OMIT_EDIT_FIELDS:
                 form_fields = form_fields.omit(field)
@@ -148,5 +148,5 @@
                 form_fields = form_fields.omit(field)
         else:
-            form_fields = grok.AutoFields(IUGApplicantEdit)
+            form_fields = grok.AutoFields(ICustomUGApplicantEdit)
             for field in UG_OMIT_EDIT_FIELDS:
                 form_fields = form_fields.omit(field)
@@ -164,9 +164,4 @@
     form_fields[
         'payment_date'].custom_widget = FriendlyDatetimeDisplayWidget('le')
-
-class CustomApplicationFeePaymentAddPage(ApplicationFeePaymentAddPage):
-    """ Page to add an online payment ticket
-    """
-    factory = u'waeup.CustomApplicantOnlinePayment'
 
 class CustomExportPDFPaymentSlipPage(ExportPDFPaymentSlipPage):
Index: main/waeup.uniben/trunk/src/waeup/uniben/applicants/export.py
===================================================================
--- main/waeup.uniben/trunk/src/waeup/uniben/applicants/export.py	(revision 8927)
+++ main/waeup.uniben/trunk/src/waeup/uniben/applicants/export.py	(revision 8928)
@@ -21,5 +21,5 @@
 from waeup.kofa.applicants.interfaces import IApplicantBaseData
 from waeup.kofa.applicants.export import ApplicantsExporter
-from waeup.uniben.applicants.interfaces import IUGApplicant, IPGApplicant
+from waeup.uniben.applicants.interfaces import ICustomUGApplicant, ICustomPGApplicant
 
 class CustomApplicantsExporter(ApplicantsExporter):
@@ -28,6 +28,6 @@
 
     fields = tuple(sorted(set(
-        IUGApplicant.names() +
-        IPGApplicant.names() +
+        ICustomUGApplicant.names() +
+        ICustomPGApplicant.names() +
         IApplicantBaseData.names()
         ))) + ('container_code',)
Index: main/waeup.uniben/trunk/src/waeup/uniben/applicants/interfaces.py
===================================================================
--- main/waeup.uniben/trunk/src/waeup/uniben/applicants/interfaces.py	(revision 8927)
+++ main/waeup.uniben/trunk/src/waeup/uniben/applicants/interfaces.py	(revision 8928)
@@ -29,6 +29,19 @@
 from waeup.kofa.students.vocabularies import nats_vocab, GenderSource
 from waeup.kofa.applicants.interfaces import contextual_reg_num_source
-from kofacustom.nigeria.interfaces import (
-    LGASource, high_qual, high_grade, exam_types)
+from kofacustom.nigeria.applicants.interfaces import (
+    LGASource, high_qual, high_grade, exam_types,
+    INigeriaUGApplicant, INigeriaPGApplicant,
+    INigeriaApplicantOnlinePayment,
+    UG_OMIT_DISPLAY_FIELDS,
+    UG_OMIT_PDF_FIELDS,
+    UG_OMIT_MANAGE_FIELDS,
+    UG_OMIT_EDIT_FIELDS,
+    PUTME_OMIT_EDIT_FIELDS,
+    UG_OMIT_RESULT_SLIP_FIELDS,
+    PG_OMIT_DISPLAY_FIELDS,
+    PG_OMIT_PDF_FIELDS,
+    PG_OMIT_MANAGE_FIELDS,
+    PG_OMIT_EDIT_FIELDS,
+    )
 from waeup.uniben.interfaces import MessageFactory as _
 from waeup.uniben.payments.interfaces import ICustomOnlinePayment
@@ -43,28 +56,5 @@
 ############################################################
 
-UG_OMIT_DISPLAY_FIELDS = ('locked', 'course_admitted',
-    'password', 'result_uploaded')
-UG_OMIT_PDF_FIELDS = UG_OMIT_DISPLAY_FIELDS + ('email', 'phone')
-UG_OMIT_MANAGE_FIELDS = ()
-UG_OMIT_EDIT_FIELDS = UG_OMIT_MANAGE_FIELDS + ('locked', 'course_admitted',
-    'student_id', 'screening_score', 'screening_venue', 'notice',
-    'screening_date', 'result_uploaded')
-PUTME_OMIT_EDIT_FIELDS = UG_OMIT_EDIT_FIELDS + (
-    'firstname', 'middlename', 'lastname', 'sex',
-    'course1', 'lga', 'jamb_score', 'jamb_subjects')
-UG_OMIT_RESULT_SLIP_FIELDS = UG_OMIT_DISPLAY_FIELDS + ('email', 'phone',
-    'date_of_birth', 'sex',
-    'nationality', 'lga', 'perm_address', 'course2', 'screening_venue',
-    'screening_date')
-
-PG_OMIT_DISPLAY_FIELDS = ('locked', 'course_admitted', 'password')
-PG_OMIT_PDF_FIELDS = UG_OMIT_DISPLAY_FIELDS + ('email', 'phone')
-PG_OMIT_MANAGE_FIELDS = ()
-PG_OMIT_EDIT_FIELDS = PG_OMIT_MANAGE_FIELDS + (
-    'locked', 'course_admitted',
-    'student_id', 'screening_score', 'screening_venue', 'notice',
-    'screening_date')
-
-class IUGApplicant(IApplicantBaseData):
+class ICustomUGApplicant(INigeriaUGApplicant):
     """An undergraduate applicant.
 
@@ -146,5 +136,5 @@
         )
 
-class IPGApplicant(IApplicantBaseData):
+class ICustomPGApplicant(INigeriaPGApplicant):
     """A postgraduate applicant.
 
@@ -325,12 +315,12 @@
         )
 
-class ICustomApplicant(IUGApplicant, IPGApplicant):
+class ICustomApplicant(ICustomUGApplicant, ICustomPGApplicant):
     """An interface for both types of applicants.
 
-    Attention: The IPGApplicant field seetings will be overwritten
-    by IPGApplicant field settings. If a field is defined
+    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 IUGApplicant. This does not affect the forms
-    since they are build on either IUGApplicant or IPGApplicant.
+    constraints in ICustomUGApplicant. This does not affect the forms
+    since they are build on either ICustomUGApplicant or ICustomPGApplicant.
     """
 
@@ -344,5 +334,5 @@
         """
 
-class IUGApplicantEdit(IUGApplicant):
+class ICustomUGApplicantEdit(ICustomUGApplicant):
     """An undergraduate applicant interface for edit forms.
 
@@ -367,10 +357,10 @@
         )
 
-IUGApplicantEdit[
-    'date_of_birth'].order =  IUGApplicant['date_of_birth'].order
-IUGApplicantEdit[
-    'email'].order =  IUGApplicant['email'].order
-
-class IPGApplicantEdit(IPGApplicant):
+ICustomUGApplicantEdit[
+    'date_of_birth'].order =  ICustomUGApplicant['date_of_birth'].order
+ICustomUGApplicantEdit[
+    'email'].order =  ICustomUGApplicant['email'].order
+
+class ICustomPGApplicantEdit(ICustomPGApplicant):
     """A postgraduate applicant interface for editing.
 
@@ -395,15 +385,15 @@
         )
 
-IPGApplicantEdit[
-    'date_of_birth'].order =  IPGApplicant['date_of_birth'].order
-IPGApplicantEdit[
-    'email'].order =  IPGApplicant['email'].order
-
-class ICustomApplicantOnlinePayment(ICustomOnlinePayment):
+ICustomPGApplicantEdit[
+    'date_of_birth'].order =  ICustomPGApplicant['date_of_birth'].order
+ICustomPGApplicantEdit[
+    'email'].order =  ICustomPGApplicant['email'].order
+
+class ICustomApplicantOnlinePayment(INigeriaApplicantOnlinePayment):
     """An applicant payment via payment gateways.
 
     """
 
-class IPUTMEApplicantEdit(IUGApplicant):
+class IPUTMEApplicantEdit(ICustomUGApplicant):
     """An undergraduate applicant interface for editing.
 
@@ -428,7 +418,7 @@
 
 IPUTMEApplicantEdit[
-    'date_of_birth'].order =  IUGApplicant['date_of_birth'].order
+    'date_of_birth'].order =  ICustomUGApplicant['date_of_birth'].order
 IPUTMEApplicantEdit[
-    'email'].order =  IUGApplicant['email'].order
+    'email'].order =  ICustomUGApplicant['email'].order
 
 class ICustomApplicantUpdateByRegNo(ICustomApplicant):
Index: main/waeup.uniben/trunk/src/waeup/uniben/applicants/payment.py
===================================================================
--- main/waeup.uniben/trunk/src/waeup/uniben/applicants/payment.py	(revision 8927)
+++ main/waeup.uniben/trunk/src/waeup/uniben/applicants/payment.py	(revision 8928)
@@ -48,5 +48,5 @@
     """
     grok.implements(IFactory)
-    grok.name(u'waeup.CustomApplicantOnlinePayment')
+    grok.name(u'waeup.ApplicantOnlinePayment')
     title = u"Create a new online payment.",
     description = u"This factory instantiates new online payment instances."
Index: main/waeup.uniben/trunk/src/waeup/uniben/applicants/tests.py
===================================================================
--- main/waeup.uniben/trunk/src/waeup/uniben/applicants/tests.py	(revision 8927)
+++ 	(revision )
@@ -1,402 +1,0 @@
-## $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
-import datetime
-import grok
-import pytz
-from zope.event import notify
-from zope.intid.interfaces import IIntIds
-from zope.interface.verify import verifyClass, verifyObject
-from zope.schema.interfaces import ConstraintNotSatisfied
-from zope.component.hooks import setSite, clearSite
-from zope.component import createObject, getUtility
-from zope.catalog.interfaces import ICatalog
-from zope.testbrowser.testing import Browser
-from hurry.workflow.interfaces import IWorkflowState
-from waeup.kofa.app import University
-from waeup.kofa.university.faculty import Faculty
-from waeup.kofa.university.department import Department
-from waeup.kofa.testing import FunctionalTestCase
-from waeup.kofa.configuration import SessionConfiguration
-from waeup.kofa.applicants.container import ApplicantsContainer
-from waeup.kofa.applicants.tests.test_batching import ApplicantImportExportSetup
-from waeup.kofa.interfaces import IBatchProcessor
-from waeup.uniben.testing import FunctionalLayer
-from waeup.uniben.applicants.export import CustomApplicantsExporter
-from waeup.uniben.applicants.batching import CustomApplicantProcessor
-
-
-class ApplicantUITest(FunctionalTestCase):
-    """Perform some browser tests.
-    """
-    layer = FunctionalLayer
-
-    def setUp(self):
-        super(ApplicantUITest, self).setUp()
-        # Setup a sample site for each test
-        app = University()
-        self.dc_root = tempfile.mkdtemp()
-        app['datacenter'].setStoragePath(self.dc_root)
-
-        # Prepopulate the ZODB...
-        self.getRootFolder()['app'] = app
-        # we add the site immediately after creation to the
-        # ZODB. Catalogs and other local utilities are not setup
-        # before that step.
-        self.app = self.getRootFolder()['app']
-        # Set site here. Some of the following setup code might need
-        # to access grok.getSite() and should get our new app then
-        setSite(app)
-
-        # Add an two different applicants containers
-        self.pgcontainer = ApplicantsContainer()
-        self.pgcontainer.code = u'pgft2011'
-        self.pgcontainer.prefix = u'pgft'
-        self.pgcontainer.application_category = u'pg_ft'
-        self.pgcontainer.year = 2011
-        self.pgcontainer.application_fee = 300.0
-        self.pgcontainer.title = u'This is the pgft2011 container'
-        self.app['applicants']['pgft2011'] = self.pgcontainer
-        self.ugcontainer = ApplicantsContainer()
-        self.ugcontainer.code = u'putme2011'
-        self.ugcontainer.prefix = u'putme'
-        self.ugcontainer.application_category = u'basic'
-        self.ugcontainer.year = 2011
-        self.ugcontainer.application_fee = 200.0
-        self.ugcontainer.title = u'This is the app2011 container'
-        self.app['applicants']['app2011'] = self.ugcontainer
-
-        self.ugcontainer.mode = 'update'
-        delta = datetime.timedelta(days=10)
-        self.ugcontainer.startdate = datetime.datetime.now(pytz.utc) - delta
-        self.ugcontainer.enddate = datetime.datetime.now(pytz.utc) + delta
-
-        # Populate university
-        self.certificate = createObject('waeup.Certificate')
-        self.certificate.code = 'CERT1'
-        self.certificate.application_category = 'basic'
-        self.certificate.start_level = 100
-        self.certificate.end_level = 500
-        self.certificate.study_mode = u'ug_ft'
-        self.app['faculties']['fac1'] = Faculty()
-        self.app['faculties']['fac1']['dep1'] = Department()
-        self.app['faculties']['fac1']['dep1'].certificates.addCertificate(
-            self.certificate)
-        self.certificate2 = createObject('waeup.Certificate')
-        self.certificate2.code = 'CERT2'
-        self.certificate2.application_category = 'pg_ft'
-        self.certificate2.start_level = 100
-        self.certificate2.end_level = 500
-        self.certificate.study_mode = u'pg_ft'
-        self.app['faculties']['fac1']['dep1'].certificates.addCertificate(
-            self.certificate2)
-
-        # Add (customized) applicants
-        pgapplicant = createObject(u'waeup.Applicant')
-        pgapplicant.firstname = u'Anna'
-        pgapplicant.lastname = u'Post'
-        self.app['applicants']['pgft2011'].addApplicant(pgapplicant)
-        self.pgapplication_number = pgapplicant.application_number
-        self.pgapplicant = self.app['applicants']['pgft2011'][
-            self.pgapplication_number]
-
-        ugapplicant = createObject(u'waeup.Applicant')
-        ugapplicant.firstname = u'Klaus'
-        ugapplicant.lastname = u'Under'
-        self.app['applicants']['app2011'].addApplicant(ugapplicant)
-        self.ugapplication_number = ugapplicant.application_number
-        self.ugapplicant = self.app['applicants']['app2011'][
-            self.ugapplication_number]
-        self.pgapplicant_path = ('http://localhost/app/applicants/pgft2011/%s'
-            % self.pgapplication_number)
-        self.ugapplicant_path = ('http://localhost/app/applicants/app2011/%s'
-            % self.ugapplication_number)
-
-        self.browser = Browser()
-        self.browser.handleErrors = False
-
-    def tearDown(self):
-        super(ApplicantUITest, self).tearDown()
-        shutil.rmtree(self.dc_root)
-        clearSite()
-        return
-
-    def fill_correct_values(self):
-        self.browser.getControl(name="form.reg_number").value = '1234'
-        self.browser.getControl(name="form.firstname").value = 'John'
-        self.browser.getControl(name="form.lastname").value = 'Tester'
-        self.browser.getControl(name="form.date_of_birth").value = '09/09/1988'
-        self.browser.getControl(name="form.lga").value = ['foreigner']
-        self.browser.getControl(name="form.nationality").value = ['NG']
-        self.browser.getControl(name="form.sex").value = ['m']
-        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
-
-    def test_manage_and_view_applicant(self):
-        # Managers can manage pg applicants.
-        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
-        # The IPGApplicant interface is really used in all pages.
-        self.browser.open(self.pgapplicant_path)
-        self.assertEqual(self.browser.headers['Status'], '200 Ok')
-        self.assertTrue('Employer' in self.browser.contents)
-        self.browser.open(self.pgapplicant_path + '/manage')
-        self.assertEqual(self.browser.headers['Status'], '200 Ok')
-        self.assertTrue('Employer' in self.browser.contents)
-        self.browser.open(self.pgapplicant_path + '/edit')
-        self.assertEqual(self.browser.headers['Status'], '200 Ok')
-        self.assertTrue('Employer' in self.browser.contents)
-        self.browser.open(self.pgapplicant_path + '/application_slip.pdf')
-        self.assertEqual(self.browser.headers['Status'], '200 Ok')
-        # If we view the applicant in the ug container,
-        # the employer field doesn't appear.
-        self.browser.open(self.ugapplicant_path)
-        self.assertEqual(self.browser.headers['Status'], '200 Ok')
-        self.assertFalse('Employer' in self.browser.contents)
-        self.browser.open(self.ugapplicant_path + '/manage')
-        self.assertEqual(self.browser.headers['Status'], '200 Ok')
-        # We can save the applicant.
-        self.fill_correct_values()
-        self.browser.getControl(name="form.course1").value = ['CERT1']
-        self.browser.getControl("Save").click()
-        self.assertMatches('...Form has been saved...', self.browser.contents)
-        self.assertFalse('Employer' in self.browser.contents)
-        self.browser.open(self.ugapplicant_path + '/edit')
-        self.assertEqual(self.browser.headers['Status'], '200 Ok')
-        self.assertFalse('Employer' in self.browser.contents)
-        self.browser.open(self.ugapplicant_path + '/application_slip.pdf')
-        self.assertEqual(self.browser.headers['Status'], '200 Ok')
-        return
-
-    def test_set_wrong_course1(self):
-        self.ugapplicant.course1 = self.certificate
-        self.assertRaises(
-            ConstraintNotSatisfied,
-            setattr, self.ugapplicant, 'course1', self.certificate2)
-        self.pgapplicant.course1 = self.certificate2
-        self.assertRaises(
-            ConstraintNotSatisfied,
-            setattr, self.pgapplicant, 'course1', self.certificate)
-        return
-
-    def test_application_payment(self):
-        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
-
-        # UG (UTME) applicant
-        self.browser.open(self.ugapplicant_path)
-        self.browser.open(self.ugapplicant_path + '/manage')
-        self.assertEqual(self.browser.headers['Status'], '200 Ok')
-        self.fill_correct_values()
-        self.browser.getControl(name="form.course1").value = ['CERT1']
-        self.browser.getControl("Save").click()
-        self.assertMatches('...Form has been saved...', self.browser.contents)
-        self.browser.getControl("Save").click()
-        self.browser.getControl("Add online payment ticket").click()
-        self.assertMatches('...Payment ticket created...',
-                           self.browser.contents)
-        self.assertMatches('...Amount Authorized...',
-                           self.browser.contents)
-        payment_id = self.ugapplicant.keys()[0]
-        payment = self.ugapplicant[payment_id]
-        self.assertEqual(payment.p_item,'This is the app2011 container')
-        self.assertEqual(payment.p_session,2011)
-        self.assertEqual(payment.p_category,'application')
-        self.assertEqual(payment.amount_auth, 200.0)
-
-        # PG applicants pay more
-        self.browser.open(self.pgapplicant_path)
-        self.browser.open(self.pgapplicant_path + '/manage')
-        self.assertEqual(self.browser.headers['Status'], '200 Ok')
-        self.fill_correct_values()
-        self.browser.getControl(name="form.course1").value = ['CERT2']
-        self.browser.getControl(name="form.reg_number").value = '2345'
-        self.browser.getControl(name="form.firstname").value = 'Anna'
-        self.browser.getControl("Save").click()
-        self.assertMatches('...Form has been saved...', self.browser.contents)
-        self.browser.getControl("Add online payment ticket").click()
-        self.assertMatches('...Payment ticket created...',
-                           self.browser.contents)
-        self.assertMatches('...Amount Authorized...',
-                           self.browser.contents)
-        payment_id = self.pgapplicant.keys()[0]
-        payment = self.pgapplicant[payment_id]
-        self.assertEqual(payment.p_item,'This is the pgft2011 container')
-        self.assertEqual(payment.p_session,2011)
-        self.assertEqual(payment.p_category,'application')
-        self.assertEqual(payment.amount_auth, 300.0)
-        return
-
-    def test_register_applicant_update(self):
-        # An applicant can register himself.
-        self.ugapplicant.reg_number = u'1234'
-        notify(grok.ObjectModifiedEvent(self.ugapplicant))
-        self.browser.open('http://localhost/app/applicants/app2011/')
-        self.browser.getLink("Register for application").click()
-        # Fill the edit form with suitable values
-        self.browser.getControl(name="form.firstname").value = 'Klaus'
-        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
-        self.browser.getControl(name="form.reg_number").value = '1234'
-        self.browser.getControl("Get login credentials").click()
-        self.assertMatches('...Your registration was successful...',
-            self.browser.contents)
-        self.assertFalse('...<td>Password:</td>...' in self.browser.contents)
-        # The new applicant can be found in the catalog via the email address
-        cat = getUtility(ICatalog, name='applicants_catalog')
-        results = list(
-            cat.searchResults(email=('xx@yy.zz', 'xx@yy.zz')))
-        applicant = results[0]
-        self.assertEqual(applicant.lastname,'Under')
-        # The applicant can be found in the catalog via the reg_number
-        results = list(
-            cat.searchResults(
-            reg_number=(applicant.reg_number, applicant.reg_number)))
-        self.assertEqual(applicant,results[0])
-        return
-
-    def test_create_ugstudent(self):
-        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
-        manage_path = 'http://localhost/app/applicants/%s/%s/%s' % (
-            'app2011', self.ugapplicant.application_number, 'manage')
-        self.browser.open(manage_path)
-        self.fill_correct_values()
-        self.browser.getControl("Save").click()
-        IWorkflowState(self.ugapplicant).setState('admitted')
-        self.browser.getControl(name="form.course1").value = ['CERT1']
-        self.browser.getControl(name="form.course_admitted").value = ['CERT1']
-        self.browser.getControl("Save").click()
-        self.browser.getLink("Create student").click()
-        student_id =  self.app['students'].keys()[0]
-        self.assertTrue(('Student %s created' % student_id)
-            in self.browser.contents)
-        student = self.app['students'][student_id]
-        self.assertEqual(student.email, 'xx@yy.zz')
-        self.assertEqual(student.firstname, 'John')
-        self.assertEqual(student.lastname, 'Tester')
-        # Also additional attributes have been copied.
-        self.assertEqual(student.lga, 'foreigner')
-        self.assertEqual(student.nationality, 'NG')
-        return
-
-class ApplicantsExporterTest(ApplicantImportExportSetup):
-
-    layer = FunctionalLayer
-
-    def setUp(self):
-        super(ApplicantsExporterTest, 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.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
-
-    def test_export_reimport_all(self):
-        # we can export all applicants in a portal
-        # set values we can expect in export file
-        self.applicant = self.setup_applicant(self.applicant)
-        exporter = CustomApplicantsExporter()
-        exporter.export_all(self.app, self.outfile)
-        result = open(self.outfile, 'rb').read()
-        # The exported records do contain a real date in their
-        # history dict. We skip the date and split the comparison
-        # into two parts.
-        self.assertTrue(
-            'aggregate,applicant_id,application_date,application_number,course1,course2,'
-            'course_admitted,date_of_birth,display_fullname,email,emp2_end,'
-            'emp2_position,emp2_reason,emp2_start,emp_end,emp_position,'
-            'emp_reason,emp_start,employer,employer2,firstname,history,'
-            'hq_degree,hq_disc,hq_matric_no,hq_school,hq_session,hq_type,'
-            'jamb_score,jamb_subjects,lastname,lga,locked,middlename,'
-            'nationality,notice,nysc_lga,'
-            'nysc_year,password,perm_address,phone,pp_school,presently_inst,'
-            'reg_number,result_uploaded,screening_date,screening_score,screening_venue,sex,'
-            'state,student_id,'
-            'container_code'
-            in result)
-        self.assertTrue(
-            'Application initialized by system\'],,,,,,,,,Tester,,0,M.,,'
-            '"Some notice\nin lines.",,,any password,,+234-123-12345,,,'
-            '123456,,"Saturday, 16th June 2012 2:00:00 PM",98,Exam Room,f,'
-            'initialized,,dp2011'
-            in result)
-        # We can import the same file if we ignore some columns.
-        # Since the applicants_catalog hasn't been notified, the same
-        # record with same reg_number can be imported twice.
-        processor = CustomApplicantProcessor()
-        result = processor.doImport(
-            self.outfile,
-            ['aggreagate','ignore_applicant_id','application_date',
-            'ignore_application_number','course1','course2',
-            'course_admitted','date_of_birth','ignore3','email','emp2_end',
-            'emp2_position','emp2_reason','emp2_start','emp_end','emp_position',
-            'emp_reason','emp_start','employer','employer2','firstname','ignore4',
-            'hq_degree','hq_disc','hq_matric_no','hq_school','hq_session','hq_type',
-            'jamb_score','jamb_subjects','lastname','lga','locked','middlename',
-            'nationality','notice','nysc_lga',
-            'nysc_year','password','perm_address','phone','pp_school','presently_inst',
-            'reg_number','result_uploaded',
-            'screening_date','screening_score','screening_venue','sex',
-            'state','student_id','container_code'],
-            mode='create')
-        num_succ, num_fail, finished_path, failed_path = result
-        self.assertEqual(num_succ,1)
-        self.assertEqual(num_fail,0)
-        # Now we ignore also the container_code and import the same file 
-        # in update mode which means that ICustomApplicantUpdateByRegNo 
-        # is used for field conversion. applicant_id must be ignored
-        # too since the previous import has notified the applicants_catalog
-        # so that the portal 'knows' that reg_number is in use.
-        processor = CustomApplicantProcessor()
-        result = processor.doImport(
-            self.outfile,
-            ['aggregate','ignore_applicant_id','application_date',
-            'ignore_application_number','course1','course2',
-            'course_admitted','date_of_birth','ignore3','email','emp2_end',
-            'emp2_position','emp2_reason','emp2_start','emp_end','emp_position',
-            'emp_reason','emp_start','employer','employer2','firstname','ignore4',
-            'hq_degree','hq_disc','hq_matric_no','hq_school','hq_session','hq_type',
-            'jamb_score','jamb_subjects','lastname','lga','locked','middlename',
-            'nationality','notice','nysc_lga',
-            'nysc_year','password','perm_address','phone','pp_school','presently_inst',
-            'reg_number','result_uploaded','screening_date','screening_score',
-            'screening_venue','sex',
-            'state','student_id','ignore_container_code'],
-            mode='update')
-        num_succ, num_fail, finished_path, failed_path = result
-        self.assertEqual(num_succ,1)
-        self.assertEqual(num_fail,0)
-        return
Index: main/waeup.uniben/trunk/src/waeup/uniben/applicants/tests/__init__.py
===================================================================
--- main/waeup.uniben/trunk/src/waeup/uniben/applicants/tests/__init__.py	(revision 8928)
+++ main/waeup.uniben/trunk/src/waeup/uniben/applicants/tests/__init__.py	(revision 8928)
@@ -0,0 +1,1 @@
+# Make this a package.
Index: main/waeup.uniben/trunk/src/waeup/uniben/applicants/tests/test_applicant.py
===================================================================
--- main/waeup.uniben/trunk/src/waeup/uniben/applicants/tests/test_applicant.py	(revision 8928)
+++ main/waeup.uniben/trunk/src/waeup/uniben/applicants/tests/test_applicant.py	(revision 8928)
@@ -0,0 +1,79 @@
+## $Id$
+##
+## Copyright (C) 2011 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+"""Tests for applicants and related.
+"""
+import grok
+import shutil
+import tempfile
+from StringIO import StringIO
+from zope.component import adapts, queryUtility
+from zope.component.interfaces import IFactory
+from zope.interface import verify, implements
+from zope.location.interfaces import ILocation
+from waeup.kofa.image.interfaces import IKofaImageFile
+from waeup.kofa.imagestorage import DefaultStorage
+from waeup.kofa.interfaces import IFileStoreHandler, IFileStoreNameChooser
+from waeup.kofa.testing import FunctionalTestCase
+from waeup.uniben.applicants.applicant import (
+    CustomApplicant, CustomApplicantFactory,
+    )
+from waeup.uniben.applicants.interfaces import ICustomApplicant
+from waeup.uniben.testing import FunctionalLayer
+
+class CustomApplicantTest(FunctionalTestCase):
+
+    layer = FunctionalLayer
+
+    def setUp(self):
+        super(CustomApplicantTest, self).setUp()
+        self.applicant = CustomApplicant()
+        return
+
+    def tearDown(self):
+        super(CustomApplicantTest, self).tearDown()
+        return
+
+    def test_interfaces(self):
+        verify.verifyClass(ICustomApplicant, CustomApplicant)
+        verify.verifyObject(ICustomApplicant, self.applicant)
+        return
+
+class CustomApplicantFactoryTest(FunctionalTestCase):
+
+    layer = FunctionalLayer
+
+    def setUp(self):
+        super(CustomApplicantFactoryTest, self).setUp()
+        self.factory = CustomApplicantFactory()
+        return
+
+    def tearDown(self):
+        super(CustomApplicantFactoryTest, self).tearDown()
+        return
+
+    def test_interfaces(self):
+        verify.verifyClass(IFactory, CustomApplicantFactory)
+        verify.verifyObject(IFactory, self.factory)
+
+    def test_factory(self):
+        obj = self.factory()
+        assert isinstance(obj, CustomApplicant)
+
+    def test_getInterfaces(self):
+        implemented_by = self.factory.getInterfaces()
+        assert implemented_by.isOrExtends(ICustomApplicant)
Index: main/waeup.uniben/trunk/src/waeup/uniben/applicants/tests/test_browser.py
===================================================================
--- main/waeup.uniben/trunk/src/waeup/uniben/applicants/tests/test_browser.py	(revision 8928)
+++ main/waeup.uniben/trunk/src/waeup/uniben/applicants/tests/test_browser.py	(revision 8928)
@@ -0,0 +1,402 @@
+## $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
+import datetime
+import grok
+import pytz
+from zope.event import notify
+from zope.intid.interfaces import IIntIds
+from zope.interface.verify import verifyClass, verifyObject
+from zope.schema.interfaces import ConstraintNotSatisfied
+from zope.component.hooks import setSite, clearSite
+from zope.component import createObject, getUtility
+from zope.catalog.interfaces import ICatalog
+from zope.testbrowser.testing import Browser
+from hurry.workflow.interfaces import IWorkflowState
+from waeup.kofa.app import University
+from waeup.kofa.university.faculty import Faculty
+from waeup.kofa.university.department import Department
+from waeup.kofa.testing import FunctionalTestCase
+from waeup.kofa.configuration import SessionConfiguration
+from waeup.kofa.applicants.container import ApplicantsContainer
+from waeup.kofa.applicants.tests.test_batching import ApplicantImportExportSetup
+from waeup.kofa.interfaces import IBatchProcessor
+from waeup.uniben.testing import FunctionalLayer
+from waeup.uniben.applicants.export import CustomApplicantsExporter
+from waeup.uniben.applicants.batching import CustomApplicantProcessor
+
+
+class ApplicantUITest(FunctionalTestCase):
+    """Perform some browser tests.
+    """
+    layer = FunctionalLayer
+
+    def setUp(self):
+        super(ApplicantUITest, self).setUp()
+        # Setup a sample site for each test
+        app = University()
+        self.dc_root = tempfile.mkdtemp()
+        app['datacenter'].setStoragePath(self.dc_root)
+
+        # Prepopulate the ZODB...
+        self.getRootFolder()['app'] = app
+        # we add the site immediately after creation to the
+        # ZODB. Catalogs and other local utilities are not setup
+        # before that step.
+        self.app = self.getRootFolder()['app']
+        # Set site here. Some of the following setup code might need
+        # to access grok.getSite() and should get our new app then
+        setSite(app)
+
+        # Add an two different applicants containers
+        self.pgcontainer = ApplicantsContainer()
+        self.pgcontainer.code = u'pgft2011'
+        self.pgcontainer.prefix = u'pgft'
+        self.pgcontainer.application_category = u'pg_ft'
+        self.pgcontainer.year = 2011
+        self.pgcontainer.application_fee = 300.0
+        self.pgcontainer.title = u'This is the pgft2011 container'
+        self.app['applicants']['pgft2011'] = self.pgcontainer
+        self.ugcontainer = ApplicantsContainer()
+        self.ugcontainer.code = u'putme2011'
+        self.ugcontainer.prefix = u'putme'
+        self.ugcontainer.application_category = u'basic'
+        self.ugcontainer.year = 2011
+        self.ugcontainer.application_fee = 200.0
+        self.ugcontainer.title = u'This is the app2011 container'
+        self.app['applicants']['app2011'] = self.ugcontainer
+
+        self.ugcontainer.mode = 'update'
+        delta = datetime.timedelta(days=10)
+        self.ugcontainer.startdate = datetime.datetime.now(pytz.utc) - delta
+        self.ugcontainer.enddate = datetime.datetime.now(pytz.utc) + delta
+
+        # Populate university
+        self.certificate = createObject('waeup.Certificate')
+        self.certificate.code = 'CERT1'
+        self.certificate.application_category = 'basic'
+        self.certificate.start_level = 100
+        self.certificate.end_level = 500
+        self.certificate.study_mode = u'ug_ft'
+        self.app['faculties']['fac1'] = Faculty()
+        self.app['faculties']['fac1']['dep1'] = Department()
+        self.app['faculties']['fac1']['dep1'].certificates.addCertificate(
+            self.certificate)
+        self.certificate2 = createObject('waeup.Certificate')
+        self.certificate2.code = 'CERT2'
+        self.certificate2.application_category = 'pg_ft'
+        self.certificate2.start_level = 100
+        self.certificate2.end_level = 500
+        self.certificate.study_mode = u'pg_ft'
+        self.app['faculties']['fac1']['dep1'].certificates.addCertificate(
+            self.certificate2)
+
+        # Add (customized) applicants
+        pgapplicant = createObject(u'waeup.Applicant')
+        pgapplicant.firstname = u'Anna'
+        pgapplicant.lastname = u'Post'
+        self.app['applicants']['pgft2011'].addApplicant(pgapplicant)
+        self.pgapplication_number = pgapplicant.application_number
+        self.pgapplicant = self.app['applicants']['pgft2011'][
+            self.pgapplication_number]
+
+        ugapplicant = createObject(u'waeup.Applicant')
+        ugapplicant.firstname = u'Klaus'
+        ugapplicant.lastname = u'Under'
+        self.app['applicants']['app2011'].addApplicant(ugapplicant)
+        self.ugapplication_number = ugapplicant.application_number
+        self.ugapplicant = self.app['applicants']['app2011'][
+            self.ugapplication_number]
+        self.pgapplicant_path = ('http://localhost/app/applicants/pgft2011/%s'
+            % self.pgapplication_number)
+        self.ugapplicant_path = ('http://localhost/app/applicants/app2011/%s'
+            % self.ugapplication_number)
+
+        self.browser = Browser()
+        self.browser.handleErrors = False
+
+    def tearDown(self):
+        super(ApplicantUITest, self).tearDown()
+        shutil.rmtree(self.dc_root)
+        clearSite()
+        return
+
+    def fill_correct_values(self):
+        self.browser.getControl(name="form.reg_number").value = '1234'
+        self.browser.getControl(name="form.firstname").value = 'John'
+        self.browser.getControl(name="form.lastname").value = 'Tester'
+        self.browser.getControl(name="form.date_of_birth").value = '09/09/1988'
+        self.browser.getControl(name="form.lga").value = ['foreigner']
+        self.browser.getControl(name="form.nationality").value = ['NG']
+        self.browser.getControl(name="form.sex").value = ['m']
+        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
+
+    def test_manage_and_view_applicant(self):
+        # Managers can manage pg applicants.
+        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
+        # The IPGApplicant interface is really used in all pages.
+        self.browser.open(self.pgapplicant_path)
+        self.assertEqual(self.browser.headers['Status'], '200 Ok')
+        self.assertTrue('Employer' in self.browser.contents)
+        self.browser.open(self.pgapplicant_path + '/manage')
+        self.assertEqual(self.browser.headers['Status'], '200 Ok')
+        self.assertTrue('Employer' in self.browser.contents)
+        self.browser.open(self.pgapplicant_path + '/edit')
+        self.assertEqual(self.browser.headers['Status'], '200 Ok')
+        self.assertTrue('Employer' in self.browser.contents)
+        self.browser.open(self.pgapplicant_path + '/application_slip.pdf')
+        self.assertEqual(self.browser.headers['Status'], '200 Ok')
+        # If we view the applicant in the ug container,
+        # the employer field doesn't appear.
+        self.browser.open(self.ugapplicant_path)
+        self.assertEqual(self.browser.headers['Status'], '200 Ok')
+        self.assertFalse('Employer' in self.browser.contents)
+        self.browser.open(self.ugapplicant_path + '/manage')
+        self.assertEqual(self.browser.headers['Status'], '200 Ok')
+        # We can save the applicant.
+        self.fill_correct_values()
+        self.browser.getControl(name="form.course1").value = ['CERT1']
+        self.browser.getControl("Save").click()
+        self.assertMatches('...Form has been saved...', self.browser.contents)
+        self.assertFalse('Employer' in self.browser.contents)
+        self.browser.open(self.ugapplicant_path + '/edit')
+        self.assertEqual(self.browser.headers['Status'], '200 Ok')
+        self.assertFalse('Employer' in self.browser.contents)
+        self.browser.open(self.ugapplicant_path + '/application_slip.pdf')
+        self.assertEqual(self.browser.headers['Status'], '200 Ok')
+        return
+
+    def test_set_wrong_course1(self):
+        self.ugapplicant.course1 = self.certificate
+        self.assertRaises(
+            ConstraintNotSatisfied,
+            setattr, self.ugapplicant, 'course1', self.certificate2)
+        self.pgapplicant.course1 = self.certificate2
+        self.assertRaises(
+            ConstraintNotSatisfied,
+            setattr, self.pgapplicant, 'course1', self.certificate)
+        return
+
+    def test_application_payment(self):
+        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
+
+        # UG (UTME) applicant
+        self.browser.open(self.ugapplicant_path)
+        self.browser.open(self.ugapplicant_path + '/manage')
+        self.assertEqual(self.browser.headers['Status'], '200 Ok')
+        self.fill_correct_values()
+        self.browser.getControl(name="form.course1").value = ['CERT1']
+        self.browser.getControl("Save").click()
+        self.assertMatches('...Form has been saved...', self.browser.contents)
+        self.browser.getControl("Save").click()
+        self.browser.getControl("Add online payment ticket").click()
+        self.assertMatches('...Payment ticket created...',
+                           self.browser.contents)
+        self.assertMatches('...Amount Authorized...',
+                           self.browser.contents)
+        payment_id = self.ugapplicant.keys()[0]
+        payment = self.ugapplicant[payment_id]
+        self.assertEqual(payment.p_item,'This is the app2011 container')
+        self.assertEqual(payment.p_session,2011)
+        self.assertEqual(payment.p_category,'application')
+        self.assertEqual(payment.amount_auth, 200.0)
+
+        # PG applicants pay more
+        self.browser.open(self.pgapplicant_path)
+        self.browser.open(self.pgapplicant_path + '/manage')
+        self.assertEqual(self.browser.headers['Status'], '200 Ok')
+        self.fill_correct_values()
+        self.browser.getControl(name="form.course1").value = ['CERT2']
+        self.browser.getControl(name="form.reg_number").value = '2345'
+        self.browser.getControl(name="form.firstname").value = 'Anna'
+        self.browser.getControl("Save").click()
+        self.assertMatches('...Form has been saved...', self.browser.contents)
+        self.browser.getControl("Add online payment ticket").click()
+        self.assertMatches('...Payment ticket created...',
+                           self.browser.contents)
+        self.assertMatches('...Amount Authorized...',
+                           self.browser.contents)
+        payment_id = self.pgapplicant.keys()[0]
+        payment = self.pgapplicant[payment_id]
+        self.assertEqual(payment.p_item,'This is the pgft2011 container')
+        self.assertEqual(payment.p_session,2011)
+        self.assertEqual(payment.p_category,'application')
+        self.assertEqual(payment.amount_auth, 300.0)
+        return
+
+    def test_register_applicant_update(self):
+        # An applicant can register himself.
+        self.ugapplicant.reg_number = u'1234'
+        notify(grok.ObjectModifiedEvent(self.ugapplicant))
+        self.browser.open('http://localhost/app/applicants/app2011/')
+        self.browser.getLink("Register for application").click()
+        # Fill the edit form with suitable values
+        self.browser.getControl(name="form.firstname").value = 'Klaus'
+        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
+        self.browser.getControl(name="form.reg_number").value = '1234'
+        self.browser.getControl("Get login credentials").click()
+        self.assertMatches('...Your registration was successful...',
+            self.browser.contents)
+        self.assertFalse('...<td>Password:</td>...' in self.browser.contents)
+        # The new applicant can be found in the catalog via the email address
+        cat = getUtility(ICatalog, name='applicants_catalog')
+        results = list(
+            cat.searchResults(email=('xx@yy.zz', 'xx@yy.zz')))
+        applicant = results[0]
+        self.assertEqual(applicant.lastname,'Under')
+        # The applicant can be found in the catalog via the reg_number
+        results = list(
+            cat.searchResults(
+            reg_number=(applicant.reg_number, applicant.reg_number)))
+        self.assertEqual(applicant,results[0])
+        return
+
+    def test_create_ugstudent(self):
+        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
+        manage_path = 'http://localhost/app/applicants/%s/%s/%s' % (
+            'app2011', self.ugapplicant.application_number, 'manage')
+        self.browser.open(manage_path)
+        self.fill_correct_values()
+        self.browser.getControl("Save").click()
+        IWorkflowState(self.ugapplicant).setState('admitted')
+        self.browser.getControl(name="form.course1").value = ['CERT1']
+        self.browser.getControl(name="form.course_admitted").value = ['CERT1']
+        self.browser.getControl("Save").click()
+        self.browser.getLink("Create student").click()
+        student_id =  self.app['students'].keys()[0]
+        self.assertTrue(('Student %s created' % student_id)
+            in self.browser.contents)
+        student = self.app['students'][student_id]
+        self.assertEqual(student.email, 'xx@yy.zz')
+        self.assertEqual(student.firstname, 'John')
+        self.assertEqual(student.lastname, 'Tester')
+        # Also additional attributes have been copied.
+        self.assertEqual(student.lga, 'foreigner')
+        self.assertEqual(student.nationality, 'NG')
+        return
+
+class ApplicantsExporterTest(ApplicantImportExportSetup):
+
+    layer = FunctionalLayer
+
+    def setUp(self):
+        super(ApplicantsExporterTest, 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.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
+
+    def test_export_reimport_all(self):
+        # we can export all applicants in a portal
+        # set values we can expect in export file
+        self.applicant = self.setup_applicant(self.applicant)
+        exporter = CustomApplicantsExporter()
+        exporter.export_all(self.app, self.outfile)
+        result = open(self.outfile, 'rb').read()
+        # The exported records do contain a real date in their
+        # history dict. We skip the date and split the comparison
+        # into two parts.
+        self.assertTrue(
+            'aggregate,applicant_id,application_date,application_number,course1,course2,'
+            'course_admitted,date_of_birth,display_fullname,email,emp2_end,'
+            'emp2_position,emp2_reason,emp2_start,emp_end,emp_position,'
+            'emp_reason,emp_start,employer,employer2,firstname,history,'
+            'hq_degree,hq_disc,hq_matric_no,hq_school,hq_session,hq_type,'
+            'jamb_score,jamb_subjects,lastname,lga,locked,middlename,'
+            'nationality,notice,nysc_lga,'
+            'nysc_year,password,perm_address,phone,pp_school,presently_inst,'
+            'reg_number,result_uploaded,screening_date,screening_score,screening_venue,sex,'
+            'state,student_id,'
+            'container_code'
+            in result)
+        self.assertTrue(
+            'Application initialized by system\'],,,,,,,,,Tester,,0,M.,,'
+            '"Some notice\nin lines.",,,any password,,+234-123-12345,,,'
+            '123456,,"Saturday, 16th June 2012 2:00:00 PM",98,Exam Room,f,'
+            'initialized,,dp2011'
+            in result)
+        # We can import the same file if we ignore some columns.
+        # Since the applicants_catalog hasn't been notified, the same
+        # record with same reg_number can be imported twice.
+        processor = CustomApplicantProcessor()
+        result = processor.doImport(
+            self.outfile,
+            ['aggreagate','ignore_applicant_id','application_date',
+            'ignore_application_number','course1','course2',
+            'course_admitted','date_of_birth','ignore3','email','emp2_end',
+            'emp2_position','emp2_reason','emp2_start','emp_end','emp_position',
+            'emp_reason','emp_start','employer','employer2','firstname','ignore4',
+            'hq_degree','hq_disc','hq_matric_no','hq_school','hq_session','hq_type',
+            'jamb_score','jamb_subjects','lastname','lga','locked','middlename',
+            'nationality','notice','nysc_lga',
+            'nysc_year','password','perm_address','phone','pp_school','presently_inst',
+            'reg_number','result_uploaded',
+            'screening_date','screening_score','screening_venue','sex',
+            'state','student_id','container_code'],
+            mode='create')
+        num_succ, num_fail, finished_path, failed_path = result
+        self.assertEqual(num_succ,1)
+        self.assertEqual(num_fail,0)
+        # Now we ignore also the container_code and import the same file 
+        # in update mode which means that ICustomApplicantUpdateByRegNo 
+        # is used for field conversion. applicant_id must be ignored
+        # too since the previous import has notified the applicants_catalog
+        # so that the portal 'knows' that reg_number is in use.
+        processor = CustomApplicantProcessor()
+        result = processor.doImport(
+            self.outfile,
+            ['aggregate','ignore_applicant_id','application_date',
+            'ignore_application_number','course1','course2',
+            'course_admitted','date_of_birth','ignore3','email','emp2_end',
+            'emp2_position','emp2_reason','emp2_start','emp_end','emp_position',
+            'emp_reason','emp_start','employer','employer2','firstname','ignore4',
+            'hq_degree','hq_disc','hq_matric_no','hq_school','hq_session','hq_type',
+            'jamb_score','jamb_subjects','lastname','lga','locked','middlename',
+            'nationality','notice','nysc_lga',
+            'nysc_year','password','perm_address','phone','pp_school','presently_inst',
+            'reg_number','result_uploaded','screening_date','screening_score',
+            'screening_venue','sex',
+            'state','student_id','ignore_container_code'],
+            mode='update')
+        num_succ, num_fail, finished_path, failed_path = result
+        self.assertEqual(num_succ,1)
+        self.assertEqual(num_fail,0)
+        return
