Index: main/waeup.kofa/trunk/src/waeup/kofa/students/export.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/export.py	(revision 8014)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/export.py	(revision 8015)
@@ -6,5 +6,6 @@
 from waeup.kofa.interfaces import ICSVExporter
 from waeup.kofa.interfaces import MessageFactory as _
-from waeup.kofa.students.interfaces import IStudent, IStudentStudyCourse
+from waeup.kofa.students.interfaces import (
+    IStudent, IStudentStudyCourse, IStudentStudyLevel,)
 from waeup.kofa.utils.batching import ExporterBase
 from waeup.kofa.utils.helpers import iface_names
@@ -107,2 +108,63 @@
                          if x is not None]
         return self.export(study_courses, filepath)
+
+class StudentStudyLevelExporter(grok.GlobalUtility, ExporterBase):
+    """Exporter for StudentStudyLevels.
+    """
+    grok.implements(ICSVExporter)
+    grok.name('studentstudylevels')
+
+    #: Fieldnames considered by this exporter
+    fields = tuple(sorted(iface_names(IStudentStudyLevel) + [
+        'reg_number', 'matric_number', 'level']))
+
+    #: The title under which this exporter will be displayed
+    title = _(u'Student Study Levels')
+
+    def mangle_value(self, value, name, context=None):
+        """Add the hash symbol at the end of date_of_birth.
+
+        This is to avoid annoying automatic date transformation
+        by Excel or Calc.
+        """
+
+        if name == 'reg_number' and context is not None:
+            value = getattr(
+                getattr(getattr(context, '__parent__', None),
+                        '__parent__', None), 'reg_number', None)
+        if name == 'matric_number' and context is not None:
+            value = getattr(
+                getattr(getattr(context, '__parent__', None),
+                        '__parent__', None), 'matric_number', None)
+        return super(
+            StudentStudyLevelExporter, self).mangle_value(
+            value, name, context=context)
+
+    def export(self, study_levels, filepath=None):
+        """Export `study_levels`, an iterable, as CSV file.
+
+        If `filepath` is ``None``, a raw string with CSV data is returned.
+        """
+        writer, outfile = self.get_csv_writer(filepath)
+        for study_level in study_levels:
+            self.write_item(study_level, writer)
+        return self.close_outfile(filepath, outfile)
+
+    def export_all(self, site, filepath=None):
+        """Export study levels into filepath as CSV data.
+
+        If `filepath` is ``None``, a raw string with CSV data is returned.
+        """
+        catalog = queryUtility(
+            ICatalog, context=site, name='students_catalog', default=None)
+        if catalog is None:
+            return self.export([], filepath)
+        students = catalog.searchResults(
+            student_id=(None, None))
+        levels = []
+        study_courses = [x.get('studycourse', None) for x in students
+                         if x is not None]
+        for course in study_courses:
+            for level in course.values():
+                levels.append(level)
+        return self.export(levels, filepath)
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_export.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_export.py	(revision 8014)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_export.py	(revision 8015)
@@ -7,6 +7,7 @@
 from waeup.kofa.interfaces import ICSVExporter
 from waeup.kofa.students.export import (
-    StudentsExporter, StudentStudyCourseExporter)
+    StudentsExporter, StudentStudyCourseExporter, StudentStudyLevelExporter,)
 from waeup.kofa.students.student import Student, StudentStudyCourse
+from waeup.kofa.students.studylevel import StudentStudyLevel
 from waeup.kofa.students.tests.test_batching import StudentImportExportSetup
 from waeup.kofa.testing import FunctionalLayer
@@ -197,2 +198,107 @@
             )
         return
+
+class StudentStudyLevelExporterTest(StudentImportExportSetup):
+
+    layer = FunctionalLayer
+
+    def setUp(self):
+        super(StudentStudyLevelExporterTest, self).setUp()
+        student = Student()
+        student.student_id = u'A111111'
+        self.app['students'][student.student_id] = self.student = student
+        self.outfile = os.path.join(self.workdir, 'myoutput.csv')
+        self.cat = getUtility(ICatalog, name='students_catalog')
+        self.intids = getUtility(IIntIds)
+        #self.study_level = StudyLevel()
+        return
+
+    def test_ifaces(self):
+        # make sure we fullfill interface contracts
+        obj = StudentStudyLevelExporter()
+        verifyObject(ICSVExporter, obj)
+        verifyClass(ICSVExporter, StudentStudyLevelExporter)
+        return
+
+    def test_get_as_utility(self):
+        # we can get an student exporter as utility
+        result = queryUtility(ICSVExporter, name="studentstudylevels")
+        self.assertTrue(result is not None)
+        return
+
+    def setup_student(self, student):
+        # set predictable values for `student`
+        student.matric_number = u'M123456'
+        student.adm_code = u'my adm code'
+        student.clearance_locked = False
+        student.clr_code = u'my clr code'
+        student.perm_address = u'Studentroad 21\nLagos 123456\n'
+        student.reg_number = u'123456'
+        student.student_id = u'A111111'
+        student.firstname = u'Anna'
+        student.lastname = u'Tester'
+        student.middlename = u'M.'
+        student.date_of_birth = datetime.date(1981, 2, 4)
+        student.sex = 'f'
+        student.email = 'anna@sample.com'
+        student.phone = u'+234-123-12345'
+        student.notice = u'Some notice\nin lines.'
+
+        study_course = student.get('studycourse')
+        student['studycourse'].certificate = self.certificate
+        student['studycourse'].entry_mode = 'ug_ft'
+        student['studycourse'].entry_session = 2010
+        student['studycourse'].current_session = 2012
+        student['studycourse'].current_level = int(self.certificate.start_level)
+
+        study_level = StudentStudyLevel()
+        study_level.level_session = 2012
+        study_level.level_verdict = "A"
+        study_level.level = 100
+        student['studycourse'].addStudentStudyLevel(
+            self.certificate, study_level)
+        return student
+
+    def test_export_empty(self):
+        # we can export a nearly empty study level
+        study_level = StudentStudyLevel()
+        exporter = StudentStudyLevelExporter()
+        exporter.export([study_level], self.outfile)
+        result = open(self.outfile, 'rb').read()
+        self.assertEqual(
+            result,
+            'level,level_session,level_verdict,matric_number,reg_number\r\n'
+            ',,0,,\r\n'
+            )
+        return
+
+    def test_export(self):
+        # we can really export study levels.
+        # set values we can expect in export file
+        self.setup_student(self.student)
+        study_course = self.student.get('studycourse')
+        study_level = study_course[study_course.keys()[0]]
+        exporter = StudentStudyLevelExporter()
+        exporter.export([study_level], self.outfile)
+        result = open(self.outfile, 'rb').read()
+        self.assertEqual(
+            result,
+            'level,level_session,level_verdict,matric_number,reg_number\r\n'
+            '100,2012,A,M123456,123456\r\n'
+            )
+        return
+
+    def test_export_all(self):
+        # we can really export study levels
+        # set values we can expect in export file
+        self.setup_student(self.student)
+        exporter = StudentStudyLevelExporter()
+        exporter.export_all(self.app, self.outfile)
+        result = open(self.outfile, 'rb').read()
+        print [x for x in self.student['studycourse'].keys()]
+        self.assertEqual(
+            result,
+            'level,level_session,level_verdict,matric_number,reg_number\r\n'
+            '100,2012,A,M123456,123456\r\n'
+            )
+        return
