Index: main/waeup.kofa/trunk/src/waeup/kofa/students/browser.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/browser.py	(revision 10177)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/browser.py	(revision 10178)
@@ -1044,4 +1044,28 @@
         return
 
+class StudyCourseTranscriptPage(KofaDisplayFormPage):
+    """ Page to display the student's transcript.
+    """
+    grok.context(IStudentStudyCourse)
+    grok.name('transcript')
+    grok.require('waeup.viewStudent')
+    grok.template('transcript')
+    pnav = 4
+
+    def update(self):
+        super(StudyCourseTranscriptPage, self).update()
+        self.semester_dict = getUtility(IKofaUtils).SEMESTER_DICT
+        self.session_dict = dict(
+            [(item[1], item[0]) for item in academic_sessions()])
+        self.studymode_dict = getUtility(IKofaUtils).STUDY_MODES_DICT
+        return
+
+    @property
+    def label(self):
+        # Here we know that the cookie has been set
+        lang = self.request.cookies.get('kofa.language')
+        return _('${a}: Transcript Data', mapping = {
+            'a':self.context.student.display_fullname})
+
 class StudentTransferFormPage(KofaAddFormPage):
     """Page to transfer the student.
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/browser_templates/transcript.pt
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/browser_templates/transcript.pt	(revision 10178)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/browser_templates/transcript.pt	(revision 10178)
@@ -0,0 +1,60 @@
+<tal:def define="tdata python:context.getTranscriptData()">
+<table class="form-table" i18n:domain="waeup.kofa">
+  <thead>
+  </thead>
+  <tbody>
+    <tal:block repeat="widget view/widgets">
+      <tr tal:condition="python: 'current' not in widget.name and 'verdict' not in widget.name">
+        <td class="filedname">
+          <span tal:content="widget/label">label</span>:
+        </td>
+        <td tal:on-error="default">
+          <div tal:content="structure widget">
+            <input type="text" />
+          </div>
+        </td>
+      </tr>
+    </tal:block>
+    <tr>
+      <td class="filedname">
+        <span i18n:translate="">Final GPA</span>:
+      </td>
+      <td>
+        <div tal:content="python:tdata[1]">GPA</div>
+      </td>
+    </tr>
+  </tbody>
+</table>
+
+<h3 i18n:domain="waeup.kofa" i18n:translate="">Subjects/Courses</h3>
+
+<table i18n:domain="waeup.kofa">
+  <thead>
+    <tr>
+      <th i18n:translate="">Session</th>
+      <th i18n:translate="">Semester</th>
+      <th i18n:translate="">Course</th>
+      <th i18n:translate="">Title</th>
+      <th i18n:translate="">Credits</th>
+      <th i18n:translate="">Grade</th>
+    </tr>
+  </thead>
+  <tbody>
+    <tal:levels tal:repeat="level python:tdata[0]">
+      <tal:semesters tal:repeat="semester_tickets python:(level['tickets_1'],
+                                      level['tickets_2'], level['tickets_3'])">
+      <tr tal:repeat="ticket semester_tickets" class="gradeC">
+        <td tal:content="python: view.session_dict[ticket.level_session]">SESSION</td>
+        <td tal:content="python: view.semester_dict[ticket.semester]">SEMESTER</td>
+        <td>
+          <span tal:content="ticket/code">CODE</span>
+        </td>
+        <td tal:content="ticket/title">TITLE</td>
+        <td tal:content="ticket/credits">CREDITS</td>
+        <td tal:content="ticket/grade|nothing">GRADE</td>
+      </tr>
+      </tal:semesters>
+    </tal:levels>
+  </tbody>
+</table>
+</tal:def>
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/studycourse.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/studycourse.py	(revision 10177)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/studycourse.py	(revision 10178)
@@ -118,4 +118,36 @@
         return
 
+    def getTranscriptData(self):
+        """Get a sorted list of dicts with level and course ticket data.
+
+        This method is used for transcripts.
+        """
+        levels = []
+        gpa = 0.0
+        credits_counted = 0
+        for level_key, level_val in self.items():
+            tickets_1 = []
+            tickets_2 = []
+            tickets_3 = []
+            for ticket in level_val.values():
+                if ticket.semester == 1:
+                    tickets_1.append(ticket)
+                elif ticket.semester == 2:
+                    tickets_2.append(ticket)
+                elif ticket.semester == 3:
+                    tickets_3.append(ticket)
+                if ticket.score:
+                    credits_counted += ticket.credits
+                    gpa += ticket.credits * ticket.weight
+            levels.append(
+                dict(level_key=level_key,
+                     level=level_val,
+                     tickets_1=tickets_1,
+                     tickets_2=tickets_2,
+                     tickets_3=tickets_3))
+        if credits_counted:
+            gpa = round(gpa/credits_counted, 2)
+        return sorted(levels, key=lambda level: level['level_key']), gpa
+
 StudentStudyCourse = attrs_to_fields(StudentStudyCourse)
 
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 10177)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_browser.py	(revision 10178)
@@ -1811,4 +1811,35 @@
             'temp_password generated: %s' % temp_password in logcontent)
 
+    def test_transcrtipts(self):
+        studylevel = createObject(u'waeup.StudentStudyLevel')
+        studylevel.level = 100
+        studylevel.level_session = 2005
+        self.student['studycourse'].addStudentStudyLevel(
+            self.certificate, studylevel)
+        # Add second course
+        courseticket = createObject('waeup.CourseTicket')
+        courseticket.code = 'ANYCODE'
+        courseticket.title = u'Any TITLE'
+        courseticket.credits = 13
+        courseticket.score = 66
+        courseticket.semester = 1
+        courseticket.dcode = u'ANYDCODE'
+        courseticket.fcode = u'ANYFCODE'
+        self.student['studycourse']['100']['COURSE2'] = courseticket
+        self.student['studycourse']['100']['COURSE1'].score = 55
+        self.assertEqual(self.student['studycourse']['100'].gpa, 3.57)
+        # Get transcript data
+        td = self.student['studycourse'].getTranscriptData()
+        self.assertEqual(td[0][0]['level_key'], '100')
+        self.assertEqual(td[0][0]['level'].level, 100)
+        self.assertEqual(td[0][0]['level'].level_session, 2005)
+        self.assertEqual(td[0][0]['tickets_1'][0].code, 'COURSE1')
+        self.assertEqual(td[0][0]['tickets_1'][1].code, 'ANYCODE')
+        self.assertEqual(td[1], 3.57)
+        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
+        self.browser.open(self.student_path + '/studycourse/transcript')
+        self.assertEqual(self.browser.headers['Status'], '200 Ok')
+        self.assertTrue('Transcript' in self.browser.contents)
+
 class StudentUITests(StudentsFullSetup):
     # Tests for Student class views and pages
