Index: main/waeup.kofa/trunk/src/waeup/kofa/browser/batchprocessing.txt
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/browser/batchprocessing.txt	(revision 9418)
+++ main/waeup.kofa/trunk/src/waeup/kofa/browser/batchprocessing.txt	(revision 9420)
@@ -98,5 +98,5 @@
     'Applicant Processor', 'Applicants Container Processor',
     'CertificateCourse Processor', 'Certificate Processor',
-    'Course Processor', 'Course Result Processor (special processor)',
+    'Course Processor',
     'CourseTicket Processor',
     'Department Processor', 'Faculty Processor',
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/batching.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/batching.py	(revision 9418)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/batching.py	(revision 9420)
@@ -43,5 +43,5 @@
     IStudent, IStudentStudyCourse,
     IStudentUpdateByRegNo, IStudentUpdateByMatricNo,
-    IStudentStudyLevel, ICourseTicket,
+    IStudentStudyLevel, ICourseTicketImport,
     IStudentOnlinePayment, IStudentVerdictUpdate)
 from waeup.kofa.students.workflow import  (
@@ -305,5 +305,5 @@
     grok.baseclass()
 
-    # additional available  fields
+    # additional available fields
     # beside 'student_id', 'reg_number' and 'matric_number'
     additional_fields = []
@@ -541,5 +541,5 @@
 
     name = u'CourseTicket Processor'
-    iface = ICourseTicket
+    iface = ICourseTicketImport
     factory_name = 'waeup.CourseTicket'
 
@@ -547,4 +547,13 @@
     additional_fields = ['level', 'code']
     additional_headers = ['level', 'code']
+
+    @property
+    def available_fields(self):
+        fields = [
+            'student_id','reg_number','matric_number',
+            'mandatory', 'score', 'carry_over', 'automatic',
+            'level_session'
+            ] + self.additional_fields
+        return sorted(fields)
 
     def getParent(self, row, site):
@@ -579,8 +588,8 @@
         obj.dcode = entries[0].__parent__.__parent__.code
         obj.title = entries[0].title
-        if getattr(obj, 'credits', None) is None:
-            obj.credits = entries[0].credits
-        if getattr(obj, 'passmark', None) is None:
-            obj.passmark = entries[0].passmark
+        #if getattr(obj, 'credits', None) is None:
+        obj.credits = entries[0].credits
+        #if getattr(obj, 'passmark', None) is None:
+        obj.passmark = entries[0].passmark
         obj.semester = entries[0].semester
         parent[row['code']] = obj
@@ -602,6 +611,7 @@
         errs, inv_errs, conv_dict = super(
             CourseTicketProcessor, self).checkConversion(row, mode=mode)
-
-        # We have to check if course really exists.
+        if mode == 'remove':
+            return errs, inv_errs, conv_dict
+        # In update and create mode we have to check if course really exists.
         # This is not done by the converter.
         catalog = getUtility(ICatalog, name='courses_catalog')
@@ -610,4 +620,17 @@
             errs.append(('code','non-existent'))
             return errs, inv_errs, conv_dict
+        # If level_session is provided in row we have to check if
+        # the parent studylevel exists and if its level_session
+        # attribute corresponds with the expected value in row.
+        level_session = row.get('level_session', IGNORE_MARKER)
+        if level_session not in (IGNORE_MARKER, ''):
+            site = grok.getSite()
+            studylevel = self.getParent(row, site)
+            if studylevel is not None:
+                if studylevel.level_session != level_session:
+                    errs.append(('level_session','does not match %s'
+                        % studylevel.level_session))
+            else:
+                errs.append(('level','does not exist'))
         return errs, inv_errs, conv_dict
 
@@ -793,78 +816,2 @@
         notify(grok.ObjectModifiedEvent(obj.__parent__))
         return
-
-class CourseResultProcessor(CourseTicketProcessor):
-    """A special batch processor for course results objects.
-
-    Import course results, compares session and creates study level.
-    """
-    grok.implements(IBatchProcessor)
-    grok.provides(IBatchProcessor)
-    grok.context(Interface)
-    util_name = 'courseresultprocessor'
-    grok.name(util_name)
-
-    name = u'Course Result Processor (special processor)'
-    iface = ICourseTicket
-    factory_name = 'waeup.CourseTicket'
-
-    location_fields = []
-    additional_fields = ['level', 'code']
-    additional_headers = ['level', 'code']
-
-    def getParent(self, row, site):
-        student = self._getStudent(row, site)
-        if student is None:
-            return None
-        return student['studycourse'].get(row['level'])
-
-    def getEntry(self, row, site):
-        level = self.getParent(row, site)
-        if level is None:
-            return None
-        return level.get(row['code'])
-
-    def updateEntry(self, obj, row, site):
-        """Update obj to the values given in row.
-        """
-        items_changed = super(CourseTicketProcessor, self).updateEntry(
-            obj, row, site)
-        parent = self.getParent(row, site)
-        student = self.getParent(row, site).__parent__.__parent__
-        student.__parent__.logger.info(
-            '%s - Course ticket in %s updated: %s'
-            % (student.student_id,  parent.level, items_changed))
-        return
-
-    def addEntry(self, obj, row, site):
-        parent = self.getParent(row, site)
-        catalog = getUtility(ICatalog, name='courses_catalog')
-        entries = list(catalog.searchResults(code=(row['code'],row['code'])))
-        obj.fcode = entries[0].__parent__.__parent__.__parent__.code
-        obj.dcode = entries[0].__parent__.__parent__.code
-        obj.title = entries[0].title
-        if getattr(obj, 'credits', None) is None:
-            obj.credits = entries[0].credits
-        if getattr(obj, 'passmark', None) is None:
-            obj.passmark = entries[0].passmark
-        obj.semester = entries[0].semester
-        parent[row['code']] = obj
-        return
-
-    def delEntry(self, row, site):
-        raise NotImplementedError('method not implemented')
-
-    def checkConversion(self, row, mode='ignore'):
-        """Validates all values in row.
-        """
-        errs, inv_errs, conv_dict = super(
-            CourseTicketProcessor, self).checkConversion(row, mode=mode)
-
-        # We have to check if course really exists.
-        # This is not done by the converter.
-        catalog = getUtility(ICatalog, name='courses_catalog')
-        entries = catalog.searchResults(code=(row['code'],row['code']))
-        if len(entries) == 0:
-            errs.append(('code','non-existent'))
-            return errs, inv_errs, conv_dict
-        return errs, inv_errs, conv_dict
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/browser.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/browser.py	(revision 9418)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/browser.py	(revision 9420)
@@ -1184,6 +1184,5 @@
     grok.require('waeup.manageStudent')
     label = _('Add course ticket')
-    form_fields = grok.AutoFields(ICourseTicketAdd).omit(
-        'score', 'automatic', 'carry_over', 'credits', 'passmark')
+    form_fields = grok.AutoFields(ICourseTicketAdd)
     pnav = 4
 
@@ -1222,5 +1221,4 @@
     grok.require('waeup.viewStudent')
     form_fields = grok.AutoFields(ICourseTicket)
-    grok.template('courseticketpage')
     pnav = 4
 
@@ -1237,6 +1235,13 @@
     grok.name('manage')
     grok.require('waeup.manageStudent')
-    form_fields = grok.AutoFields(ICourseTicket).omit('credits', 'passmark')
-    grok.template('courseticketmanagepage')
+    form_fields = grok.AutoFields(ICourseTicket)
+    form_fields['title'].for_display = True
+    form_fields['fcode'].for_display = True
+    form_fields['dcode'].for_display = True
+    form_fields['semester'].for_display = True
+    form_fields['passmark'].for_display = True
+    form_fields['credits'].for_display = True
+    form_fields['mandatory'].for_display = True
+    form_fields['automatic'].for_display = True
     pnav = 4
 
@@ -2181,6 +2186,5 @@
     grok.name('ctadd')
     grok.require('waeup.handleStudent')
-    form_fields = grok.AutoFields(ICourseTicketAdd).omit(
-        'score', 'mandatory', 'automatic', 'carry_over', 'credits', 'passmark')
+    form_fields = grok.AutoFields(ICourseTicketAdd)
 
     def update(self):
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/browser_templates/courseticketmanagepage.pt
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/browser_templates/courseticketmanagepage.pt	(revision 9418)
+++ 	(revision )
@@ -1,62 +1,0 @@
-<form action="." tal:attributes="action request/URL" method="post"
-      i18n:domain="waeup.kofa" enctype="multipart/form-data">
-
-  <table class="form-table">
-    <tbody>
-      <tr>
-        <td class="fieldname" i18n:translate="">
-            Course Title:
-        </td>
-        <td>
-            <span tal:replace="context/title">TITLE</span>
-        </td>
-      </tr>
-      <tr>
-        <td class="fieldname" i18n:translate="">
-            Faculty:
-        </td>
-        <td>
-            <span tal:replace="context/fcode">FACULTY</span>
-        </td>
-      </tr>
-      <tr>
-        <td class="fieldname" i18n:translate="">
-            Department:
-        </td>
-        <td>
-            <span tal:replace="context/dcode">DEPARTMENT</span>
-        </td>
-      </tr>
-      <tr>
-        <td class="fieldname" i18n:translate="">
-            Semester:
-        </td>
-        <td>
-            <span tal:replace="context/semester">SEMESTER</span>
-        </td>
-      </tr>
-      <tr>
-        <td class="fieldname" i18n:translate="">
-            Credits:
-        </td>
-        <td>
-            <span tal:replace="context/credits">CREDITS</span>
-        </td>
-      </tr>
-      <tr>
-        <td class="fieldname" i18n:translate="">
-            Passmark:
-        </td>
-        <td>
-            <span tal:replace="context/passmark">PASSMARK</span>
-        </td>
-      </tr>
-      <tal:widgets content="structure provider:widgets" />
-    </tbody>
-  </table>
-  <div tal:condition="view/availableActions">
-    <input tal:repeat="action view/actions"
-           tal:replace="structure action/render"
-           />
-  </div>
-</form>
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/browser_templates/courseticketpage.pt
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/browser_templates/courseticketpage.pt	(revision 9418)
+++ 	(revision )
@@ -1,62 +1,0 @@
-<table class="form-table" i18n:domain="waeup.kofa">
-  <tbody>
-    <tr>
-      <td class="fieldname">
-          Course Title:
-      </td>
-      <td>
-          <span tal:replace="context/title">TITLE</span>
-      </td>
-    </tr>
-    <tr>
-      <td class="fieldname">
-          Department:
-      </td>
-      <td>
-          <span tal:replace="context/dcode">DEPARTMENT</span>
-      </td>
-    </tr>
-    <tr>
-      <td class="fieldname">
-          Faculty:
-      </td>
-      <td>
-          <span tal:replace="context/fcode">FACULTY</span>
-      </td>
-    </tr>
-    <tr>
-      <td class="fieldname">
-          Semester:
-      </td>
-      <td>
-          <span tal:replace="context/semester">SEMESTER</span>
-      </td>
-    </tr>
-    <tr>
-      <td class="fieldname">
-          Credits:
-      </td>
-      <td>
-          <span tal:replace="context/credits">CREDITS</span>
-      </td>
-    </tr>
-    <tr>
-      <td class="fieldname">
-          Passmark:
-      </td>
-      <td>
-          <span tal:replace="context/passmark">PASSMARK</span>
-      </td>
-    </tr>
-    <tal:block repeat="widget view/widgets">
-      <tr>
-        <td class="fieldname">
-          <tal:block content="widget/label"/>:
-        </td>
-        <td>
-          <input tal:replace="structure widget" />
-        </td>
-      </tr>
-    </tal:block>
-  </tbody>
-</table>
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/export.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/export.py	(revision 9418)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/export.py	(revision 9420)
@@ -210,6 +210,5 @@
     #: Fieldnames considered by this exporter
     fields = tuple(sorted(iface_names(ICourseTicket) +
-        ['level', 'code', 'title',
-        'semester', 'fcode', 'dcode'])) + ('student_id', 'certcode')
+        ['level', 'code'])) + ('student_id', 'certcode')
 
     #: The title under which this exporter will be displayed
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/interfaces.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/interfaces.py	(revision 9418)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/interfaces.py	(revision 9420)
@@ -28,10 +28,10 @@
 from waeup.kofa.students.vocabularies import (
     StudyLevelSource, contextual_reg_num_source, contextual_mat_num_source,
-    GenderSource, nats_vocab,
+    GenderSource, nats_vocab
     )
 from waeup.kofa.payments.interfaces import (
     IPaymentsContainer, IOnlinePayment)
 from waeup.kofa.university.vocabularies import (
-    CourseSource, StudyModeSource, CertificateSource)
+    CourseSource, StudyModeSource, CertificateSource, SemesterSource)
 
 # VerdictSource can't be placed into the vocabularies module because it
@@ -488,15 +488,40 @@
 
 class ICourseTicket(IKofaObject):
-    """A course ticket.
+    """An interface for course tickets.
 
     """
     code = Attribute('code of the original course')
-    title = Attribute('title of the original course')
-    credits = Attribute('credits of the original course')
-    passmark = Attribute('passmark of the original course')
-    semester = Attribute('semester of the original course')
-    fcode = Attribute('faculty code of the original course')
-    dcode = Attribute('department code of the original course')
     certcode = Attribute('certificate code of the study course')
+
+    title = schema.TextLine(
+        title = _(u'Title'),
+        required = False,
+        )
+
+    fcode = schema.TextLine(
+        title = _(u'Faculty Code'),
+        required = False,
+        )
+
+    dcode = schema.TextLine(
+        title = _(u'Department Code'),
+        required = False,
+        )
+
+    semester = schema.Choice(
+        title = _(u'Semester/Term'),
+        source = SemesterSource(),
+        required = False,
+        )
+
+    passmark = schema.Int(
+        title = _(u'Passmark'),
+        required = False,
+        )
+
+    credits = schema.Int(
+        title = _(u'Credits'),
+        required = False,
+        )
 
     mandatory = schema.Bool(
@@ -504,5 +529,4 @@
         default = False,
         required = False,
-        readonly = False,
         )
 
@@ -511,5 +535,10 @@
         default = 0,
         required = False,
-        readonly = False,
+        )
+
+    carry_over = schema.Bool(
+        title = _(u'Carry-over Course'),
+        default = False,
+        required = False,
         )
 
@@ -518,23 +547,6 @@
         default = False,
         required = False,
-        readonly = True,
-        )
-
-    carry_over = schema.Bool(
-        title = _(u'Carry-over Course'),
-        default = False,
-        required = False,
-        readonly = False,
-        )
-
-    credits = schema.Int(
-        title = _(u'Credits'),
-        required = False,
-        )
-
-    passmark = schema.Int(
-        title = _(u'Passmark'),
-        required = False,
-        )
+        )
+
 
     def getLevel():
@@ -546,5 +558,5 @@
         """
 
-class ICourseTicketAdd(ICourseTicket):
+class ICourseTicketAdd(IKofaObject):
     """An interface for adding course tickets.
 
@@ -556,4 +568,21 @@
         )
 
+class ICourseTicketImport(ICourseTicket):
+    """An interface for importing course results and nothing more.
+
+    """
+    score = schema.Int(
+        title = _(u'Score'),
+        required = False,
+        readonly = False,
+        )
+
+    level_session = schema.Choice(
+        title = _(u'Level Session'),
+        source = academic_sessions_vocab,
+        required = False,
+        readonly = False,
+        )
+
 class IStudentAccommodation(IKofaObject):
     """A container for student accommodation objects.
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/studylevel.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/studylevel.py	(revision 9418)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/studylevel.py	(revision 9420)
@@ -121,8 +121,4 @@
         super(CourseTicket, self).__init__()
         self.code = None
-        self.title = None
-        self.fcode = None
-        self.dcode = None
-        self.semester = None
         return
 
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/tests/sample_courseticket_data.csv
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/tests/sample_courseticket_data.csv	(revision 9418)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/tests/sample_courseticket_data.csv	(revision 9420)
@@ -1,6 +1,9 @@
-reg_number,matric_number,level,code,score,mandatory
-1,,100,COURSE1,1,True
-2,,100,COURSE1,2,False
-,100002,100,COURSE1,3,False
-1,,nonsense,COURSE1,5,
-1,,100,NONSENSE,5,
+reg_number,matric_number,level,code,score,mandatory,level_session
+1,,100,COURSE1,1,True,
+2,,100,COURSE1,2,False,
+,100002,100,COURSE1,3,False,
+1,,nonsense,COURSE1,5,,
+1,,100,NONSENSE,5,,
+1,,200,COURSE1,6,,2004
+1,,300,COURSE1,6,,2008
+1,,300,COURSE1,6,,200888
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 9418)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_batching.py	(revision 9420)
@@ -733,9 +733,15 @@
 
     def test_import(self):
-
         num, num_warns, fin_file, fail_file = self.processor.doImport(
             self.csv_file, COURSETICKET_HEADER_FIELDS,'create')
-
-        self.assertEqual(num_warns,2)
+        fail_file = open(fail_file).read()
+        self.assertEqual(num_warns,5)
+        self.assertEqual(fail_file,
+            'reg_number,code,mandatory,level,level_session,score,matric_number,--ERRORS--\r\n'
+            '1,COURSE1,,nonsense,,5,,Not all parents do exist yet. Skipping\r\n'
+            '1,NONSENSE,,100,,5,,code: non-existent\r\n'
+            '1,COURSE1,,200,2004,6,,level_session: does not match 2008\r\n'
+            '1,COURSE1,,300,2008,6,,level: does not exist\r\n'
+            '1,COURSE1,,300,200888,6,,level_session: Invalid value; level: does not exist\r\n')
         assert self.processor.entryExists(
             dict(reg_number='1', level='100', code='COURSE1'),
@@ -772,5 +778,13 @@
         num, num_warns, fin_file, fail_file = self.processor.doImport(
             self.csv_file, COURSETICKET_HEADER_FIELDS,'update')
-        self.assertEqual(num_warns,2)
+        fail_file = open(fail_file).read()
+        self.assertEqual(num_warns,5)
+        self.assertEqual(fail_file,
+            'reg_number,code,mandatory,level,level_session,score,matric_number,--ERRORS--\r\n'
+            '1,COURSE1,<IGNORE>,nonsense,<IGNORE>,5,<IGNORE>,Cannot update: no such entry\r\n'
+            '1,NONSENSE,<IGNORE>,100,<IGNORE>,5,<IGNORE>,code: non-existent\r\n'
+            '1,COURSE1,<IGNORE>,200,2004,6,<IGNORE>,level_session: does not match 2008\r\n'
+            '1,COURSE1,<IGNORE>,300,2008,6,<IGNORE>,level: does not exist\r\n'
+            '1,COURSE1,<IGNORE>,300,200888,6,<IGNORE>,level_session: Invalid value; level: does not exist\r\n')
         shutil.rmtree(os.path.dirname(fin_file))
 
@@ -786,5 +800,5 @@
         num, num_warns, fin_file, fail_file = self.processor.doImport(
             self.csv_file, COURSETICKET_HEADER_FIELDS,'remove')
-        self.assertEqual(num_warns,2)
+        self.assertEqual(num_warns,5)
         assert self.processor.entryExists(
             dict(reg_number='1', level='100', code='COURSE1'), self.app) is False
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_browser.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_browser.py	(revision 9418)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_browser.py	(revision 9420)
@@ -161,6 +161,6 @@
         self.certificate.school_fee_1 = 40000.0
         self.certificate.school_fee_2 = 20000.0
-        self.app['faculties']['fac1'] = Faculty(code='fac1')
-        self.app['faculties']['fac1']['dep1'] = Department(code='dep1')
+        self.app['faculties']['fac1'] = Faculty(code=u'fac1')
+        self.app['faculties']['fac1']['dep1'] = Department(code=u'dep1')
         self.app['faculties']['fac1']['dep1'].certificates.addCertificate(
             self.certificate)
