Index: main/kofacustom.skeleton/trunk/CHANGES.txt
===================================================================
--- main/kofacustom.skeleton/trunk/CHANGES.txt	(revision 11518)
+++ main/kofacustom.skeleton/trunk/CHANGES.txt	(revision 11543)
@@ -4,4 +4,6 @@
 1.2dev (unreleased)
 ===================
+
+* Adapt exporters and batch processors to customized interfaces.
 
 * Use kofa-scrollto script.
Index: main/kofacustom.skeleton/trunk/src/kofacustom/skeleton/applicants/batching.py
===================================================================
--- main/kofacustom.skeleton/trunk/src/kofacustom/skeleton/applicants/batching.py	(revision 11543)
+++ main/kofacustom.skeleton/trunk/src/kofacustom/skeleton/applicants/batching.py	(revision 11543)
@@ -0,0 +1,38 @@
+## $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 waeup.kofa.utils.batching import BatchProcessor
+from waeup.kofa.applicants.batching import ApplicantProcessor
+from kofacustom.skeleton.applicants.interfaces import (
+    ICustomApplicant, ICustomApplicantUpdateByRegNo)
+
+class CustomApplicantProcessor(ApplicantProcessor):
+    """A batch processor for IApplicant objects.
+
+    In create mode container_code is required. If application_number is given
+    an applicant with this number is created in the designated container.
+    If application_number is not given a random application_number is assigned.
+    applicant_id is being determined by the system and can't be imported.
+
+    In update or remove mode container_code and application_number columns
+    must not exist. The applicant object is solely searched by its applicant_id
+    or reg_number.
+    """
+    iface = ICustomApplicant
+    iface_byregnumber = ICustomApplicantUpdateByRegNo
Index: main/kofacustom.skeleton/trunk/src/kofacustom/skeleton/applicants/export.py
===================================================================
--- main/kofacustom.skeleton/trunk/src/kofacustom/skeleton/applicants/export.py	(revision 11543)
+++ main/kofacustom.skeleton/trunk/src/kofacustom/skeleton/applicants/export.py	(revision 11543)
@@ -0,0 +1,38 @@
+## $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 kofacustom.nigeria.applicants.export import NigeriaApplicantsExporter
+from kofacustom.nigeria.applicants.interfaces import (
+    INigeriaUGApplicant, INigeriaPGApplicant)
+from kofacustom.skeleton.applicants.interfaces import (
+    ICustomUGApplicant, ICustomPGApplicant)
+
+class CustomApplicantsExporter(NigeriaApplicantsExporter):
+    """Exporter for Custom Applicants.
+    """
+
+    fields = tuple(sorted(set(
+        ICustomUGApplicant.names() +
+        ICustomPGApplicant.names() +
+        INigeriaUGApplicant.names() +
+        INigeriaPGApplicant.names() +
+        IApplicantBaseData.names()
+        ))) + ('container_code',)
Index: main/kofacustom.skeleton/trunk/src/kofacustom/skeleton/applicants/tests/test_browser.py
===================================================================
--- main/kofacustom.skeleton/trunk/src/kofacustom/skeleton/applicants/tests/test_browser.py	(revision 11518)
+++ main/kofacustom.skeleton/trunk/src/kofacustom/skeleton/applicants/tests/test_browser.py	(revision 11543)
@@ -19,6 +19,14 @@
 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 kofacustom.skeleton.testing import FunctionalLayer
 from waeup.kofa.applicants.tests.test_browser import ApplicantsFullSetup
+from waeup.kofa.applicants.tests.test_batching import ApplicantImportExportSetup
+from kofacustom.skeleton.applicants.export import CustomApplicantsExporter
+from kofacustom.skeleton.applicants.batching import CustomApplicantProcessor
 
 class CustomApplicantUITests(ApplicantsFullSetup):
@@ -26,2 +34,111 @@
 
     layer = FunctionalLayer
+
+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.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
+
+    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()
+        self.assertMatches(result,
+            '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,phone,pp_school,presently_inst,reg_number,'
+            'result_uploaded,screening_date,screening_score,'
+            'screening_venue,sex,special_application,'
+            'state,student_id,suspended,container_code\r\n'
+            ',dp2011_654321,,654321,CERT1,CERT1,CERT1,1981-02-04#,'
+            'Anna M. Tester,anna@sample.com,,,,,,,,,,,Anna,'
+            '[u\'2012-10-28 21:27:52 WAT - Application initialized by system\']'
+            ',,,,,,,,,Tester,,0,M.,NG,"Some notice\nin lines.",,,'
+            'any password,+234-123-12345#,,,123456,,'
+            '"Saturday, 16th June 2012 2:00:00 PM",98,Exam Room,f,,initialized,,'
+            '0,dp2011\r\n')
+        # 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','phone','pp_school','presently_inst',
+            'reg_number','result_uploaded',
+            'screening_date','screening_score','screening_venue','sex',
+            'special_application',
+            'state','student_id','suspended','container_code'],
+            mode='create')
+        num_succ, num_fail, finished_path, failed_path = result
+        #content = open(failed_path).read()
+        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 INigeriaApplicantUpdateByRegNo
+        # 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','phone','pp_school','presently_inst',
+            'reg_number','result_uploaded','screening_date','screening_score',
+            'screening_venue','sex','special_application',
+            'state','student_id','suspended','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/kofacustom.skeleton/trunk/src/kofacustom/skeleton/students/export.py
===================================================================
--- main/kofacustom.skeleton/trunk/src/kofacustom/skeleton/students/export.py	(revision 11543)
+++ main/kofacustom.skeleton/trunk/src/kofacustom/skeleton/students/export.py	(revision 11543)
@@ -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
+##
+"""Exporters for student related stuff.
+"""
+from kofacustom.skeleton.students.interfaces import (
+    ICustomStudent, ICustomStudentStudyCourse, ICustomCourseTicket,
+    ICustomStudentOnlinePayment)
+from kofacustom.nigeria.students.export import (
+    NigeriaStudentsExporter, NigeriaStudentStudyCourseExporter,
+    NigeriaCourseTicketExporter, NigeriaStudentPaymentsExporter)
+from waeup.kofa.utils.helpers import iface_names
+
+class CustomStudentsExporter(NigeriaStudentsExporter):
+    """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 CustomCourseTicketExporter(NigeriaCourseTicketExporter):
+    """Exporter for CourseTickets.
+    """
+
+    fields = tuple(sorted(iface_names(ICustomCourseTicket) +
+        ['level', 'code', 'level_session'])) + ('student_id',
+        'certcode', 'display_fullname')
+
+class CustomStudentPaymentsExporter(NigeriaStudentPaymentsExporter):
+    """Exporter for OnlinePayment instances.
+    """
+
+    fields = tuple(
+        sorted(iface_names(
+            ICustomStudentOnlinePayment, exclude_attribs=False,
+            omit=['display_item']))) + (
+            'student_id','state','current_session')
+
Index: main/kofacustom.skeleton/trunk/src/kofacustom/skeleton/students/tests/test_export.py
===================================================================
--- main/kofacustom.skeleton/trunk/src/kofacustom/skeleton/students/tests/test_export.py	(revision 11543)
+++ main/kofacustom.skeleton/trunk/src/kofacustom/skeleton/students/tests/test_export.py	(revision 11543)
@@ -0,0 +1,111 @@
+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
+from kofacustom.skeleton.utils.utils import CustomKofaUtils
+from kofacustom.skeleton.students.export import (
+    CustomStudentsExporter, CustomStudentPaymentsExporter)
+from kofacustom.skeleton.testing import FunctionalLayer
+
+
+class CustomStudentsExporterTest(StudentImportExportSetup):
+
+    layer = FunctionalLayer
+
+    def setUp(self):
+        super(CustomStudentsExporterTest, 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 = CustomStudentsExporter()
+        verifyObject(ICSVExporter, obj)
+        verifyClass(ICSVExporter, CustomStudentsExporter)
+        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 = CustomStudentsExporter()
+        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,clearance_locked,'
+            '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,firstname,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,'
+            'perm_address,personal_updated,phone,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,,,,'
+            '"[(\'visual_art\', \'A1\')]",0,my clr code,1981-02-04#,,,'
+            'anna@sample.com,,,,,,,,,,,Anna,,,,,"[(\'visual_art\', \'A1\')]"'
+            ',,,,,,,,,,,,,,,,Tester,,,234,M.,NG,,,,,,,,,'
+            '"Studentroad 21\nLagos 123456\n",,+234-123-12345#,123,,,,,'
+            '"[(\'visual_art\', \'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 CustomStudentPaymentsExporterTest(StudentImportExportSetup):
+
+    layer = FunctionalLayer
+
+    def setUp(self):
+        super(CustomStudentPaymentsExporterTest, self).setUp()
+        self.setup_for_export()
+        return
+
+    def test_ifaces(self):
+        # make sure we fullfill interface contracts
+        obj = CustomStudentPaymentsExporter()
+        verifyObject(ICSVExporter, obj)
+        verifyClass(ICSVExporter, CustomStudentPaymentsExporter)
+        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 = CustomStudentPaymentsExporter()
+        exporter.export_all(self.app, self.outfile)
+        result = open(self.outfile, 'rb').read()
+        self.assertMatches(
+            'ac,amount_auth,creation_date,gateway_amt,p_category,p_current,'
+            'p_id,p_item,p_level,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,2012-04-01 13:12:01,,schoolfee,1,my-id,p-item,'
+            '100,2012,paid,2012-04-01 14:12:01,,12.12,'
+            '789,r-code,interswatch,,,,A111111,created,2012\r\n',
+            result
+            )
+        return
