Index: /main/waeup.kofa/trunk/src/waeup/kofa/students/batching.py
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/students/batching.py	(revision 7950)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/students/batching.py	(revision 7951)
@@ -31,13 +31,13 @@
 from zope.event import notify
 from zope.catalog.interfaces import ICatalog
-from hurry.workflow.interfaces import IWorkflowState
+from hurry.workflow.interfaces import IWorkflowState, IWorkflowInfo
 from waeup.kofa.interfaces import (
     IBatchProcessor, FatalCSVError, IObjectConverter, IUserAccount,
-    IObjectHistory)
+    IObjectHistory, VALIDATED)
 from waeup.kofa.students.interfaces import (
     IStudent, IStudentStudyCourse,
     IStudentUpdateByRegNo, IStudentUpdateByMatricNo,
     IStudentStudyLevel, ICourseTicket,
-    IStudentOnlinePayment)
+    IStudentOnlinePayment, IStudentVerdictUpdate)
 from waeup.kofa.students.workflow import  IMPORTABLE_STATES
 from waeup.kofa.utils.batching import BatchProcessor
@@ -639,2 +639,66 @@
                 return errs, inv_errs, conv_dict
         return errs, inv_errs, conv_dict
+
+class StudentVerdictProcessor(StudentStudyCourseProcessor):
+    """A batch processor for verdicts.
+
+    Import verdicts and perform workflow transitions.
+    """
+
+    util_name = 'verdictupdater'
+    grok.name(util_name)
+
+    name = u'Verdict Processor (update only)'
+    iface = IStudentVerdictUpdate
+    factory_name = 'waeup.StudentStudyCourse'
+
+    @property
+    def available_fields(self):
+        return sorted(list(set(
+            ['student_id','reg_number','matric_number',
+                'current_session', 'current_level'] + getFields(
+                self.iface).keys())))
+
+    def checkUpdateRequirements(self, obj, row, site):
+        """Checks requirements the studycourse and the student must fulfill
+        before being updated.
+        """
+        # Check if current_levels correspond
+        if obj.current_level != row['current_level']:
+            return 'Current level does not correspond.'
+        # Check if current_sessions correspond
+        if obj.current_session != row['current_session']:
+            return 'Current session does not correspond.'
+        # Check if student is in state REGISTERED
+        if obj.getStudent().state != VALIDATED:
+            return 'Student in wrong state.'
+        return None
+
+    def updateEntry(self, obj, row, site):
+        """Update obj to the values given in row.
+        """
+        items_changed = ''
+        for key, value in row.items():
+            # Skip fields not declared in interface plus
+            # current_verdict and current_level
+            if hasattr(obj, key) and not key in [
+                'current_verdict','current_level']:
+                setattr(obj, key, value)
+            items_changed += '%s=%s, ' % (key,value)
+        parent = self.getParent(row, site)
+        parent.__parent__.logger.info(
+            '%s - Verdict updated: %s'
+            % (parent.student_id, items_changed))
+        # Fire transition
+        IWorkflowInfo(obj.__parent__).fireTransition('return')
+        # Update the students_catalog
+        notify(grok.ObjectModifiedEvent(obj.__parent__))
+        return
+
+    def checkConversion(self, row, mode='ignore'):
+        """Validates all values in row.
+        """
+        converter = IObjectConverter(self.iface)
+        errs, inv_errs, conv_dict =  converter.fromStringDict(
+            row, self.factory_name)
+        return errs, inv_errs, conv_dict
Index: /main/waeup.kofa/trunk/src/waeup/kofa/students/interfaces.py
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/students/interfaces.py	(revision 7950)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/students/interfaces.py	(revision 7951)
@@ -334,4 +334,29 @@
         )
 
+class IStudentVerdictUpdate(IKofaObject):
+    """A interface for verdict imports.
+
+    """
+
+    current_verdict = schema.Choice(
+        title = _(u'Current Verdict'),
+        source = VerdictSource(),
+        required = True,
+        )
+
+    current_session = schema.Choice(
+        title = _(u'Current Session'),
+        source = academic_sessions_vocab,
+        default = None,
+        required = True,
+        )
+
+    current_level = schema.Choice(
+        title = _(u'Current Level'),
+        source = StudyLevelSource(),
+        default = None,
+        required = True,
+        )
+
 class IStudentStudyLevel(IKofaObject):
     """A container for course tickets.
Index: /main/waeup.kofa/trunk/src/waeup/kofa/students/tests/sample_student_data.csv
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/students/tests/sample_student_data.csv	(revision 7950)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/students/tests/sample_student_data.csv	(revision 7951)
@@ -1,4 +1,4 @@
-student_id,firstname,lastname,reg_number,date_of_birth,matric_number,email,phone
-X666666,Aaren,Pieri,1,1990-01-02,100000,aa@aa.ng,1234
-,Aaren,Finau,2,1990-01-03,100001,aa@aa.ng,1234
-,Aaren,Berson,3,1990-01-04,100002,aa@aa.ng,1234
+student_id,firstname,lastname,reg_number,date_of_birth,matric_number,email,phone,reg_state
+X666666,Aaren,Pieri,1,1990-01-02,100000,aa@aa.ng,1234,courses validated
+,Aaren,Finau,2,1990-01-03,100001,aa@aa.ng,1234,courses validated
+,Aaren,Berson,3,1990-01-04,100002,aa@aa.ng,1234,courses validated
Index: /main/waeup.kofa/trunk/src/waeup/kofa/students/tests/sample_verdict_data.csv
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/students/tests/sample_verdict_data.csv	(revision 7951)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/students/tests/sample_verdict_data.csv	(revision 7951)
@@ -0,0 +1,4 @@
+matric_number,current_session,current_level,current_verdict
+100000,2008,200,A
+100001,2008,200,B
+100002,2008,200,C
Index: /main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_batching.py
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_batching.py	(revision 7950)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_batching.py	(revision 7951)
@@ -34,5 +34,5 @@
     StudentProcessor, StudentStudyCourseProcessor,
     StudentStudyLevelProcessor, CourseTicketProcessor,
-    StudentOnlinePaymentProcessor)
+    StudentOnlinePaymentProcessor, StudentVerdictProcessor)
 from waeup.kofa.students.student import Student
 from waeup.kofa.testing import FunctionalLayer, FunctionalTestCase
@@ -65,4 +65,11 @@
 
 STUDYCOURSE_HEADER_FIELDS = STUDYCOURSE_SAMPLE_DATA.split(
+    '\n')[0].split(',')
+
+VERDICT_SAMPLE_DATA = open(
+    os.path.join(os.path.dirname(__file__), 'sample_verdict_data.csv'),
+    'rb').read()
+
+VERDICT_HEADER_FIELDS = VERDICT_SAMPLE_DATA.split(
     '\n')[0].split(',')
 
@@ -108,11 +115,4 @@
         self.app = self.getRootFolder()['app']
         setSite(app)
-
-        # Import students with subobjects
-        #student_file = os.path.join(self.workdir, 'sample_student_data.csv')
-        #open(student_file, 'wb').write(STUDENT_SAMPLE_DATA)
-        #num, num_warns, fin_file, fail_file = StudentProcessor().doImport(
-        #    student_file, STUDENT_HEADER_FIELDS)
-        #shutil.rmtree(os.path.dirname(fin_file))
 
         # Populate university
@@ -126,9 +126,4 @@
         self.app['faculties']['fac1']['dep1'].certificates.addCertificate(
             self.certificate)
-
-        self.importer = StudentStudyCourseProcessor()
-        self.csv_file = os.path.join(
-            self.workdir, 'sample_studycourse_data.csv')
-        open(self.csv_file, 'wb').write(STUDYCOURSE_SAMPLE_DATA)
         return
 
@@ -297,17 +292,8 @@
 
 
-class StudentStudyCourseProcessorTest(FunctionalTestCase):
-
-    layer = FunctionalLayer
+class StudentStudyCourseProcessorTest(StudentImportExportSetup):
 
     def setUp(self):
         super(StudentStudyCourseProcessorTest, self).setUp()
-        self.dc_root = tempfile.mkdtemp()
-        self.workdir = tempfile.mkdtemp()
-        app = University()
-        app['datacenter'].setStoragePath(self.dc_root)
-        self.getRootFolder()['app'] = app
-        self.app = self.getRootFolder()['app']
-        setSite(app)
 
         # Import students with subobjects
@@ -318,15 +304,4 @@
         shutil.rmtree(os.path.dirname(fin_file))
 
-        # Populate university
-        self.certificate = createObject('waeup.Certificate')
-        self.certificate.code = 'CERT1'
-        self.certificate.application_category = 'basic'
-        self.certificate.start_level = 200
-        self.certificate.end_level = 500
-        self.app['faculties']['fac1'] = Faculty()
-        self.app['faculties']['fac1']['dep1'] = Department()
-        self.app['faculties']['fac1']['dep1'].certificates.addCertificate(
-            self.certificate)
-
         self.processor = StudentStudyCourseProcessor()
         self.csv_file = os.path.join(
@@ -335,11 +310,4 @@
         return
 
-    def tearDown(self):
-        super(StudentStudyCourseProcessorTest, self).tearDown()
-        shutil.rmtree(self.workdir)
-        shutil.rmtree(self.dc_root)
-        clearSite()
-        return
-
     def test_interface(self):
         # Make sure we fulfill the interface contracts.
@@ -384,17 +352,9 @@
         shutil.rmtree(os.path.dirname(fin_file))
 
-class StudentStudyLevelProcessorTest(FunctionalTestCase):
-
-    layer = FunctionalLayer
+class StudentVerdictProcessorTest(StudentImportExportSetup):
+
 
     def setUp(self):
-        super(StudentStudyLevelProcessorTest, self).setUp()
-        self.dc_root = tempfile.mkdtemp()
-        self.workdir = tempfile.mkdtemp()
-        app = University()
-        app['datacenter'].setStoragePath(self.dc_root)
-        self.getRootFolder()['app'] = app
-        self.app = self.getRootFolder()['app']
-        setSite(app)
+        super(StudentVerdictProcessorTest, self).setUp()
 
         # Import students with subobjects
@@ -404,15 +364,4 @@
             student_file, STUDENT_HEADER_FIELDS)
         shutil.rmtree(os.path.dirname(fin_file))
-
-        # Populate university
-        self.certificate = createObject('waeup.Certificate')
-        self.certificate.code = 'CERT1'
-        self.certificate.application_category = 'basic'
-        self.certificate.start_level = 200
-        self.certificate.end_level = 500
-        self.app['faculties']['fac1'] = Faculty()
-        self.app['faculties']['fac1']['dep1'] = Department()
-        self.app['faculties']['fac1']['dep1'].certificates.addCertificate(
-            self.certificate)
 
         # Update study courses
@@ -425,15 +374,44 @@
         shutil.rmtree(os.path.dirname(fin_file))
 
+        self.processor = StudentVerdictProcessor()
+        self.csv_file = os.path.join(
+            self.workdir, 'sample_verdict_data.csv')
+        open(self.csv_file, 'wb').write(VERDICT_SAMPLE_DATA)
+        return
+
+    def test_import(self):
+        num, num_warns, fin_file, fail_file = self.processor.doImport(
+            self.csv_file, VERDICT_HEADER_FIELDS,'update')
+        self.assertEqual(num_warns,0)
+        studycourse = self.processor.getEntry(dict(reg_number='1'), self.app)
+        self.assertEqual(studycourse.certificate.code, u'CERT1')
+        shutil.rmtree(os.path.dirname(fin_file))
+
+
+class StudentStudyLevelProcessorTest(StudentImportExportSetup):
+
+    def setUp(self):
+        super(StudentStudyLevelProcessorTest, self).setUp()
+
+        # Import students with subobjects
+        student_file = os.path.join(self.workdir, 'sample_student_data.csv')
+        open(student_file, 'wb').write(STUDENT_SAMPLE_DATA)
+        num, num_warns, fin_file, fail_file = StudentProcessor().doImport(
+            student_file, STUDENT_HEADER_FIELDS)
+        shutil.rmtree(os.path.dirname(fin_file))
+
+        # Update study courses
+        studycourse_file = os.path.join(
+            self.workdir, 'sample_studycourse_data.csv')
+        open(studycourse_file, 'wb').write(STUDYCOURSE_SAMPLE_DATA)
+        processor = StudentStudyCourseProcessor()
+        num, num_warns, fin_file, fail_file = processor.doImport(
+            studycourse_file, STUDYCOURSE_HEADER_FIELDS,'update')
+        shutil.rmtree(os.path.dirname(fin_file))
+
         self.processor = StudentStudyLevelProcessor()
         self.csv_file = os.path.join(
             self.workdir, 'sample_studylevel_data.csv')
         open(self.csv_file, 'wb').write(STUDYLEVEL_SAMPLE_DATA)
-
-    def tearDown(self):
-        super(StudentStudyLevelProcessorTest, self).tearDown()
-        shutil.rmtree(self.workdir)
-        shutil.rmtree(self.dc_root)
-        clearSite()
-        return
 
     def test_interface(self):
@@ -471,17 +449,8 @@
         
 
-class CourseTicketProcessorTest(FunctionalTestCase):
-
-    layer = FunctionalLayer
+class CourseTicketProcessorTest(StudentImportExportSetup):
 
     def setUp(self):
         super(CourseTicketProcessorTest, self).setUp()
-        self.dc_root = tempfile.mkdtemp()
-        self.workdir = tempfile.mkdtemp()
-        app = University()
-        app['datacenter'].setStoragePath(self.dc_root)
-        self.getRootFolder()['app'] = app
-        self.app = self.getRootFolder()['app']
-        setSite(app)
 
         # Import students with subobjects
@@ -492,14 +461,5 @@
         shutil.rmtree(os.path.dirname(fin_file))
 
-        # Populate university
-        self.certificate = createObject('waeup.Certificate')
-        self.certificate.code = 'CERT1'
-        self.certificate.application_category = 'basic'
-        self.certificate.start_level = 200
-        self.certificate.end_level = 500
-        self.app['faculties']['fac1'] = Faculty()
-        self.app['faculties']['fac1']['dep1'] = Department()
-        self.app['faculties']['fac1']['dep1'].certificates.addCertificate(
-            self.certificate)
+        # Add course and course referrer
         self.course = createObject('waeup.Course')
         self.course.code = 'COURSE1'
@@ -534,11 +494,4 @@
             self.workdir, 'sample_courseticket_data.csv')
         open(self.csv_file, 'wb').write(COURSETICKET_SAMPLE_DATA)
-
-    def tearDown(self):
-        super(CourseTicketProcessorTest, self).tearDown()
-        shutil.rmtree(self.workdir)
-        shutil.rmtree(self.dc_root)
-        clearSite()
-        return
 
     def test_interface(self):
@@ -579,17 +532,8 @@
         shutil.rmtree(os.path.dirname(fin_file))
 
-class PaymentProcessorTest(FunctionalTestCase):
-
-    layer = FunctionalLayer
+class PaymentProcessorTest(StudentImportExportSetup):
 
     def setUp(self):
         super(PaymentProcessorTest, self).setUp()
-        self.dc_root = tempfile.mkdtemp()
-        self.workdir = tempfile.mkdtemp()
-        app = University()
-        app['datacenter'].setStoragePath(self.dc_root)
-        self.getRootFolder()['app'] = app
-        self.app = self.getRootFolder()['app']
-        setSite(app)
 
         # Add student with payment
@@ -616,11 +560,4 @@
             self.workdir, 'sample_payment_data.csv')
         open(self.csv_file, 'wb').write(PAYMENT_SAMPLE_DATA)
-
-    def tearDown(self):
-        super(PaymentProcessorTest, self).tearDown()
-        shutil.rmtree(self.workdir)
-        shutil.rmtree(self.dc_root)
-        clearSite()
-        return
 
     def test_interface(self):
@@ -690,5 +627,5 @@
         StudentProcessorTest,StudentStudyCourseProcessorTest,
         StudentStudyLevelProcessorTest,CourseTicketProcessorTest,
-        PaymentProcessorTest,]:
+        PaymentProcessorTest,StudentVerdictProcessorTest]:
         suite.addTest(unittest.TestLoader().loadTestsFromTestCase(
                 testcase
