Index: main/waeup.aaue/trunk/src/waeup/aaue/students/browser.py
===================================================================
--- main/waeup.aaue/trunk/src/waeup/aaue/students/browser.py	(revision 14272)
+++ main/waeup.aaue/trunk/src/waeup/aaue/students/browser.py	(revision 14288)
@@ -28,5 +28,6 @@
 from waeup.kofa.browser.layout import UtilityView
 from waeup.kofa.widgets.datewidget import FriendlyDatetimeDisplayWidget
-from waeup.kofa.interfaces import IKofaUtils, academic_sessions_vocab
+from waeup.kofa.interfaces import (
+    IKofaUtils, academic_sessions_vocab, ICSVExporter)
 from waeup.kofa.students.interfaces import IStudentsUtils, IStudent
 from waeup.kofa.students.workflow import PAID, REGISTERED, RETURNING
@@ -49,4 +50,5 @@
     ExportPDFScoresSlip,
     StudyCourseTranscriptPage,
+    DownloadScoresView,
     )
 from kofacustom.nigeria.students.browser import (
@@ -668,31 +670,7 @@
         return result
 
-    def update(self,  *args, **kw):
-        form = self.request.form
+    def _update_scores(self, form):
         ob_class = self.__implemented__.__name__.replace('waeup.kofa.', '')
-        self.current_academic_session = grok.getSite()[
-            'configuration'].current_academic_session
-        if self.context.__parent__.__parent__.score_editing_disabled:
-            self.flash(_('Score editing disabled.'), type="warning")
-            self.redirect(self.url(self.context))
-            return
-        if not self.current_academic_session:
-            self.flash(_('Current academic session not set.'), type="warning")
-            self.redirect(self.url(self.context))
-            return
-        self.session_title = academic_sessions_vocab.getTerm(
-            self.current_academic_session).title
-        self.tickets = self._searchCatalog(self.current_academic_session)
-        self.editable_tickets = [
-            ticket for ticket in self.tickets if ticket.editable_by_lecturer]
-        if not self.tickets:
-            self.flash(_('No student found.'), type="warning")
-            self.redirect(self.url(self.context))
-            return
-        if not 'UPDATE_TABLE' in form and not 'UPDATE_FILE' in form:
-            return
-
-        if not self.editable_tickets:
-            return
+        error = ''
         if 'UPDATE_FILE' in form:
             if form['uploadfile']:
@@ -703,14 +681,11 @@
                         _('Uploaded file contains illegal data. Ignored'),
                         type="danger")
-                    return
+                    return False
             else:
                 self.flash(
                     _('No file provided.'), type="danger")
-                return
+                return False
         else:
             formvals = dict(zip(form['sids'], zip(form['scores'], form['cas'])))
-        error = ''
-        # In AAUE only editable tickets are shown, see also customized
-        # pagetemplate
         for ticket in self.editable_tickets:
             ticket_error = False
@@ -754,6 +729,39 @@
             self.flash(_('Error: Score(s) and CA(s) of %s have not be updated. '
               % error.strip(', ')), type="danger")
+        return True
+
+class EditPreviousSessionScoresPage(CustomEditScoresPage):
+
+    grok.name('edit_prev_scores')
+
+    def update(self,  *args, **kw):
+        form = self.request.form
+        self.current_academic_session = grok.getSite()[
+            'configuration'].current_academic_session
+        if self.context.__parent__.__parent__.score_editing_disabled:
+            self.flash(_('Score editing disabled.'), type="warning")
+            self.redirect(self.url(self.context))
             return
-        self.flash(_('You successfully updated course results.'))
+        if not self.current_academic_session:
+            self.flash(_('Current academic session not set.'), type="warning")
+            self.redirect(self.url(self.context))
+            return
+        previous_session = self.current_academic_session - 1
+        self.session_title = academic_sessions_vocab.getTerm(
+            previous_session).title
+        self.tickets = self._searchCatalog(previous_session)
+        if not self.tickets:
+            self.flash(_('No student found.'), type="warning")
+            self.redirect(self.url(self.context))
+            return
+        self.editable_tickets = [
+            ticket for ticket in self.tickets if ticket.editable_by_lecturer]
+        if not 'UPDATE_TABLE' in form and not 'UPDATE_FILE' in form:
+            return
+        if not self.editable_tickets:
+            return
+        success = self._update_scores(form)
+        if success:
+            self.flash(_('You successfully updated course results.'))
         return
 
@@ -783,8 +791,11 @@
                    _('Grade'),
                    ],]
+        sorted_tickets = sorted(editable_tickets,
+            key=lambda ticket: ticket.student.certcode +
+                ticket.student.display_fullname + str(ticket.level))
+        no = 1
         tickets = []
-        no = 1
         # In AAUE only editable tickets can be printed
-        for ticket in editable_tickets:
+        for ticket in sorted_tickets:
             if None in (ticket.score, ticket.ca):
                 total = 'n/a'
@@ -808,4 +819,28 @@
             tickets.append(row)
             no += 1
-        return header + sorted(tickets,
-            key=lambda value: value[5] + value[3] + str(value[6]))
+        return header + tickets
+
+class DownloadPreviousSessionScoresView(DownloadScoresView):
+    """View that exports scores.
+    """
+    grok.name('download_prev_scores')
+
+    def update(self):
+        self.current_academic_session = grok.getSite()[
+            'configuration'].current_academic_session
+        if self.context.__parent__.__parent__.score_editing_disabled:
+            self.flash(_('Score editing disabled.'), type="warning")
+            self.redirect(self.url(self.context))
+            return
+        if not self.current_academic_session:
+            self.flash(_('Current academic session not set.'), type="warning")
+            self.redirect(self.url(self.context))
+            return
+        site = grok.getSite()
+        exporter = getUtility(ICSVExporter, name='lecturer')
+        self.csv = exporter.export_filtered(site, filepath=None,
+                                 catalog='coursetickets',
+                                 session=self.current_academic_session-1,
+                                 level=None,
+                                 code=self.context.code)
+        return
Index: main/waeup.aaue/trunk/src/waeup/aaue/students/browser_templates/editscorespage.pt
===================================================================
--- main/waeup.aaue/trunk/src/waeup/aaue/students/browser_templates/editscorespage.pt	(revision 14272)
+++ main/waeup.aaue/trunk/src/waeup/aaue/students/browser_templates/editscorespage.pt	(revision 14288)
@@ -4,5 +4,5 @@
   (press 'Help' for further information) or by
   changing score or CA values in the table below. Only scores and CAs
-  of students in state 'courses validated' and current session
+  of students in current session
   <span  i18n:name="current_academic_session"
          tal:replace="view/session_title"></span>
@@ -61,5 +61,7 @@
           <strong>Note:</strong>
           Only course results of students which are in
-          state 'courses validated' and in current academic session
+          academic session
+          <span  i18n:name="current_academic_session"
+            tal:replace="view/session_title"></span>
           can be modified. Additional data will just be ignored.
         </div>
Index: main/waeup.aaue/trunk/src/waeup/aaue/students/studylevel.py
===================================================================
--- main/waeup.aaue/trunk/src/waeup/aaue/students/studylevel.py	(revision 14272)
+++ main/waeup.aaue/trunk/src/waeup/aaue/students/studylevel.py	(revision 14288)
@@ -312,4 +312,10 @@
             return self.score + self.ca
 
+    @property
+    def editable_by_lecturer(self):
+        """True if lecturer is allowed to edit the ticket.
+        """
+        return True
+
 CustomCourseTicket = attrs_to_fields(CustomCourseTicket)
 
Index: main/waeup.aaue/trunk/src/waeup/aaue/students/tests/test_browser.py
===================================================================
--- main/waeup.aaue/trunk/src/waeup/aaue/students/tests/test_browser.py	(revision 14272)
+++ main/waeup.aaue/trunk/src/waeup/aaue/students/tests/test_browser.py	(revision 14288)
@@ -79,4 +79,5 @@
             'http://localhost/app/faculties/fac1/dep1/courses/COURSE1')
         self.edit_scores_url = '%s/edit_scores' % self.course_url
+        self.edit_prev_scores_url = '%s/edit_prev_scores' % self.course_url
         # Set standard parameters
         self.app['configuration'].current_academic_session = 2004
@@ -363,4 +364,31 @@
         self.student['studycourse']['100']['COURSE1'].score = 55
         self.browser.open(self.edit_scores_url)
+        upload_ctrl = self.browser.getControl(name='uploadfile:file')
+        upload_file = StringIO(UPLOAD_CSV_TEMPLATE % ('65','22'))
+        upload_ctrl.add_file(upload_file, 'text/csv', 'myscores.csv')
+        self.browser.getControl("Update editable scores from").click()
+        # value changed
+        self.assertEqual(
+            self.student['studycourse']['100']['COURSE1'].score, 65)
+        self.assertEqual(
+            self.student['studycourse']['100']['COURSE1'].ca, 22)
+
+    def test_scores_previous_session(self):
+        # lecturers can download a CSV file to set values.
+        self.login_as_lecturer()
+        self.student['studycourse']['100']['COURSE1'].score = 55
+        self.browser.open(self.edit_prev_scores_url)
+        self.assertTrue('No student found' in self.browser.contents)
+        self.student['studycourse']['100'].level_session = 2003
+        notify(grok.ObjectModifiedEvent(self.student['studycourse']['100']['COURSE1']))
+        self.browser.open(self.edit_prev_scores_url)
+        self.browser.getLink("Download csv file").click()
+        self.assertEqual(self.browser.headers['Status'], '200 Ok')
+        self.assertEqual(self.browser.headers['Content-Type'],
+                         'text/csv; charset=UTF-8')
+        self.assertEqual(self.browser.contents, 'matric_number,student_id,'
+            'level,code,level_session,ca,score\r\n234,E1000000,'
+            '100,COURSE1,2003,,55\r\n')
+        self.browser.open(self.edit_prev_scores_url)
         upload_ctrl = self.browser.getControl(name='uploadfile:file')
         upload_file = StringIO(UPLOAD_CSV_TEMPLATE % ('65','22'))
Index: main/waeup.aaue/trunk/src/waeup/aaue/students/viewlets.py
===================================================================
--- main/waeup.aaue/trunk/src/waeup/aaue/students/viewlets.py	(revision 14272)
+++ main/waeup.aaue/trunk/src/waeup/aaue/students/viewlets.py	(revision 14288)
@@ -27,5 +27,7 @@
     StudentPassportActionButton,
     StudyCourseTranscriptActionButton,
-    TranscriptSlipActionButton)
+    TranscriptSlipActionButton,
+    DownloadCSVFileActionButton,
+    DownloadTicketOverviewActionButton)
 from waeup.kofa.students.browser import StudyLevelDisplayFormPage
 
@@ -129,2 +131,23 @@
     grok.require('waeup.viewStudent')
     grok.name('transcript_slip')
+
+class CustomDownloadCSVFileActionButton(DownloadCSVFileActionButton):
+    """ 'Download csv file' button for courses.
+    """
+
+    @property
+    def target_url(self):
+        if self.view.__name__ == 'edit_prev_scores':
+            return self.view.url(self.view.context, 'download_prev_scores')
+        return self.view.url(self.view.context, 'download_scores')
+
+class CustomDownloadTicketOverviewActionButton(
+    DownloadTicketOverviewActionButton):
+    """ 'Download ticket overview' button for courses.
+    """
+
+    @property
+    def target_url(self):
+        if self.view.__name__ == 'edit_prev_scores':
+            return ''
+        return self.view.url(self.view.context, 'coursetickets.pdf')
