Index: main/waeup.kofa/trunk/docs/source/userdocs/datacenter/import_processors.rst
===================================================================
--- main/waeup.kofa/trunk/docs/source/userdocs/datacenter/import_processors.rst	(revision 13002)
+++ main/waeup.kofa/trunk/docs/source/userdocs/datacenter/import_processors.rst	(revision 13003)
@@ -136,4 +136,6 @@
   verdicts have to be set or workflow transitions have to be triggered.
 
+.. _course_ticket_processor:
+
 Course Ticket Processor
 -----------------------
Index: main/waeup.kofa/trunk/docs/source/userdocs/students.rst
===================================================================
--- main/waeup.kofa/trunk/docs/source/userdocs/students.rst	(revision 13002)
+++ main/waeup.kofa/trunk/docs/source/userdocs/students.rst	(revision 13003)
@@ -4,5 +4,5 @@
 **********************************
 
-'Student' is a multi-purpose term in Kofa which may lead to some misconceptions. First of all 'Student' is a Python/Grok container class and student objects are the instances of this class. Sometimes we are sluggish when talking about 'creating students' and actually mean: 'creating student container objects and storing the objects (persistently) in the ``students`` container'. The latter is the parent container for all students in the portal.
+'Student' is a multi-purpose term in Kofa which may lead to some misconceptions. First of all, 'Student' is a Python/Grok container class and student objects are the instances of this class. Sometimes we are sluggish when talking about 'creating students' and actually mean: 'creating student container objects and storing the objects (persistently) in the ``students`` container'. The latter is the parent container for all students in the portal.
 
 The :ref:`treelike storage of objects <object_database>` in the student section can be figured as follows::
@@ -57,5 +57,5 @@
    :pyobject: IStudentBase
 
-The **first part** of the interface lists attributes. Except for the first two (`password` and `temp_password`) they are all read-only property attributes, i.e. attributes with a getter method only. These properties are computed dynamically and can't be set. Most of them return data derived from the ``studycourse`` subobject.
+The **first part** of the interface lists attributes. Except for the last two attributes (`password` and `temp_password`) they are all read-only property attributes, i.e. attributes with a getter method only. These properties are computed dynamically and can't be set. Most of them return data derived from the ``studycourse`` subobject.
 
 The **second part** of the interface specifies the schema fields
@@ -71,5 +71,5 @@
   **Vocabularies and sources** are very similar. The latter is a newer concept of producing choices and considered to replace vocabularies. In Kofa we are using both. Common to both is that a set of values is accompanied by a user-friendly title, which is displayed on pages (e.g. in select boxes) instead of a less informative value token or even a cryptic representation of a persistent value object. In most cases we have a token-title pair which perfectly describes the values of a source or a vocabulary. The tokens are being sent in forms or imported by batch processors.
 
-  **Token-title pairs** of most (non-database-dependent) sources and vocabularies of the Kofa base package can be viewed `here <http://kofa-demo.waeup.org/sources>`_.
+  See `token-title pairs <http://kofa-demo.waeup.org/sources>`_ of most (non-database-dependent) sources and vocabularies in the Kofa base package.
 
 The **third part** of the interface lists the methods which can be applied to student objects.
@@ -107,8 +107,16 @@
 Kofa provides a mechanism which ensures that personal data are being kept up to date. Students are regularly requested to update the personal data directly after login. The personal data expire 180 days after saving the data for the last time which is stored in the `personal_updated` attribute. Then the `personal_data_expired` property returns ``True`` and the student is redirected to the personal data edit page when logging in.
 
+`IStudentNavigation`
+--------------------
+
+See docstring.
+
+.. literalinclude:: ../../../src/waeup/kofa/students/interfaces.py
+   :pyobject: IStudentNavigation
+
 Student Study Courses
 =====================
 
-All data related to an individual course of study are stored with the ``studycourse`` object. This container object is an instance of the `StudentStudyCourse` class which implements `waeup.kofa.students.interfaces.IStudentStudyCourse`.
+All data related to an individual course of study are stored with the ``studycourse`` object. This container object is an instance of the `StudentStudyCourse` class which implements `waeup.kofa.students.interfaces.IStudentStudyCourse`, `waeup.kofa.students.interfaces.IStudentNavigation` and `waeup.kofa.students.interfaces.IStudentStudyCourseTranscript`. The latter does not add further behaviour to our classes but is needed for transcript pages only. It is not described in the user handbook.
 
 `IStudentStudyCourse`
@@ -128,10 +136,10 @@
 What is a verdict? A verdict is the individual judgement of a jury (senate in Nigeria) at the end of each academic session. The jury's verdict tells the portal, if the student has successfully completed an academic session or not. Depending on the verdict, the student will be either allowed to proceed to the next study level or forced to repeat the current level. Without a verdict, the student gets stuck and cannot even pay school fees for the next academic year. This will be further exlplained in the workflow section below.
 
-`StudentStudyCourse` is a container class. Study courses contain `StudentStudyLevel` instances which implement `waeup.kofa.students.interfaces.IStudentStudyLevel`.
+`StudentStudyCourse` is a container class. Study courses contain `StudentStudyLevel` instances which implement `waeup.kofa.students.interfaces.IStudentStudyLevel` and also `waeup.kofa.students.interfaces.IStudentNavigation`, see above.
 
 `IStudentStudyLevel`
 --------------------
 
-`StudentStudyLevel` instances contain information about the study progress achieved at that level. In Kofa study levels range from 100 to 900. There are two additional levels: a pre-studies level (10) and a special postgraduate level (999). There are also two probation levels per regular study level. Level 120, for instance, means level 100 on second probation. The complete numbering of study levels can be viewed `here <https://kofa-demo.waeup.org/sources#collapseStudyLevels>`_.
+`StudentStudyLevel` instances contain information about the study progress achieved at that level. In Kofa study levels range from 100 to 900. There are two additional levels: a pre-studies level (10) and a special postgraduate level (999). There are also two probation levels per regular study level. Level 120, for instance, means level 100 on second probation. See `complete numbering of study levels <https://kofa-demo.waeup.org/sources#collapseStudyLevels>`_ in the base package.
 
 `StudentStudyLevel` instances are container objects which contain course tickets. We therefore sometimes speak of a level course list instead of a study level. The study level stores and provides the information when (`validation_date`) and by whom (`validated_by`) the course list was validated, in which session the level was taken (`level_session`) and which verdict the student finally obtained.
@@ -140,11 +148,21 @@
    :pyobject: IStudentStudyLevel
 
-`StudentStudyLevel` instances also compute some statistical values on the fly to give an overview of the courses taken. These read-only property attributes are: `number_of_tickets`, `passed_params`, `gpa_params`, `gpa_params_rectified`, `cumulative_params`, `total_credits` and `gpa`. The attentive reader may wonder why the latter two are not listed in the attributes section of the interface, but as read-only schema fields further below. This is a trick which we used to properly display these fields on some form pages and pdf slips. However, the `attrs_to_fields` function explicitly excludes them when starting the portal, so that these fields are not used for persistent storage of same-named attributes in the database.
+`StudentStudyLevel` instances also compute some statistics on the fly to give an overview of the courses taken. These read-only property attributes are: `number_of_tickets`, `passed_params`, `gpa_params`, `gpa_params_rectified`, `cumulative_params`, `total_credits` and `gpa`. The attentive reader may wonder why the latter two are not listed in the attributes section of the interface, but as read-only schema fields further below. This is a trick which we used to properly display these fields on some display pages and pdf slips. However, the `attrs_to_fields` function explicitly excludes them when starting the portal, so that these fields are not used for persistent storage of same-named attributes in the database.
 
 `ICourseTicket`
 ---------------
 
+Course tickets allow students to attend a course. Lecturers can enter scores at the end of the term. A `CourseTicket` instance contains a copy of the original `Course` and `CertificateCourse` data. If the courses and/or the referring certificate courses are removed, the corresponding tickets remain unchanged. So we do not need any event triggered actions on course tickets.
+
+The `CourseTicket`  implements `waeup.kofa.students.interfaces.ICourseTicket` and also `waeup.kofa.students.interfaces.IStudentNavigation`, see above.
+
 .. literalinclude:: ../../../src/waeup/kofa/students/interfaces.py
    :pyobject: ICourseTicket
+
+The quite long list of schema fields pretends that form pages may provide these fields for editing. This is not the case. Except for `score`, all these fields are 'for display' only. They can neither be changed through the UI nor by batch processing (import). They are solely meant for backing up the orginal course data. See also :ref:`course_ticket_processor`.
+
+.. note::
+
+  It may happen that a course has been accidentally misconfigured, for example the number of credits was set too high. The course object can be corrected, but, course tickets, which refer to this course, can not. They are neither adjusted automatically nor changeable by the batch processor. The only solution to 'adjust' course tickets is to replace them by new tickets.
 
 Student Payments
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/interfaces.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/interfaces.py	(revision 13002)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/interfaces.py	(revision 13003)
@@ -178,11 +178,11 @@
 
 class IStudentNavigation(IStudentNavigationBase):
-    """Interface needed for student navigation, logging, etc.
-    """
-    student = Attribute('Student object of context.')
+    """Interface needed for navigation and logging. This interface is
+    implemented by all content classes in the student section.
+    """
+    student = Attribute('Student object of context')
 
     def writeLogMessage(view, message):
         """Write a view specific log message into students.log.
-
         """
 
@@ -190,6 +190,4 @@
     """Representation of student base data.
     """
-    password = Attribute('Encrypted password')
-    temp_password = Attribute('Dictionary with user name, timestamp and encrypted password')
     history = Attribute('Object history, a list of messages')
     state = Attribute('Registration state')
@@ -211,4 +209,7 @@
     transcript_enabled = Attribute('True if transcript processing is enabled')
 
+    password = Attribute('Encrypted password')
+    temp_password = Attribute('Dictionary with user name, timestamp and encrypted password')
+
     suspended = schema.Bool(
         title = _(u'Account suspended'),
@@ -281,8 +282,4 @@
         required = False,
         )
-
-    def writeLogMessage(view, message):
-        """Write formatted log message into students.log.
-        """
 
     def setTempPassword(user, password):
@@ -427,5 +424,4 @@
     """Representation of student study course data.
     """
-    student = Attribute('Student object')
     next_session_allowed = Attribute('True if the student can proceed to next session')
     is_postgrad = Attribute('True if student is postgraduate student')
@@ -482,8 +478,4 @@
         )
 
-    def writeLogMessage(view, message):
-        """Write formatted log message into students.log.
-        """
-
     def addStudentStudyLevel(cert, studylevel):
         """Add a study level object.
@@ -577,6 +569,4 @@
     """A representation of student study level data. 
     """
-    student = Attribute('Student object')
-
     certcode = Attribute('The certificate code of the study course')
     is_current_level = Attribute('True if level is current level of the student')
@@ -633,8 +623,4 @@
         )
 
-    def writeLogMessage(view, message):
-        """Write formatted log message into students.log.
-        """
-
     def addCourseTicket(ticket, course):
         """Add a course ticket object.
@@ -649,11 +635,13 @@
     """A representation of course ticket data.
     """
-    code = Attribute('code of the original course')
-    certcode = Attribute('certificate code of the study course')
-    grade = Attribute('grade calculated from score')
-    weight = Attribute('weight calculated from score')
-    removable_by_student = Attribute('Is student allowed to remove the ticket?')
-    level_session = Attribute('session of the level the ticket has been added to')
-    level = Attribute('id of the level the ticket has been added to')
+    certcode = Attribute('Certificate code of the study course')
+    level_session = Attribute('Session of the study level the ticket has been added to')
+    level = Attribute('Level value of the study level the ticket has been added to')
+    grade = Attribute('Grade calculated from score')
+    weight = Attribute('Weight calculated from score')
+    removable_by_student = Attribute('True if student is allowed to remove the ticket')
+    editable_by_lecturer = Attribute('True if lecturer is allowed to edit the ticket')
+
+    code = Attribute('Code of the original course')
 
     title = schema.TextLine(
@@ -712,5 +700,4 @@
         required = False,
         )
-
 
 class ICourseTicketAdd(IKofaObject):
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/studylevel.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/studylevel.py	(revision 13002)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/studylevel.py	(revision 13003)
@@ -143,5 +143,4 @@
     def passed_params(self):
         """Determine the number and credits of passed and failed courses.
-
         This method is used for level reports.
         """
@@ -274,5 +273,5 @@
         try:
             return self.__parent__.__parent__.__parent__
-        except AttributeError:
+        except AttributeError: # in unit tests
             return None
 
@@ -281,5 +280,5 @@
         try:
             return self.__parent__.__parent__.certificate.code
-        except AttributeError:
+        except AttributeError: # in unit tests
             return None
 
@@ -290,7 +289,10 @@
     @property
     def editable_by_lecturer(self):
-        cas = grok.getSite()['configuration'].current_academic_session
-        if self.student.state == VALIDATED and self.student.current_session == cas:
-            return True
+        try:
+            cas = grok.getSite()['configuration'].current_academic_session
+            if self.student.state == VALIDATED and self.student.current_session == cas:
+                return True
+        except TypeError: # in unit tests
+            pass
         return False
 
@@ -304,5 +306,5 @@
         try:
             return self.__parent__.level
-        except AttributeError:
+        except AttributeError: # in unit tests
             return None
 
@@ -313,5 +315,5 @@
         try:
             return self.__parent__.level_session
-        except AttributeError:
+        except AttributeError: # in unit tests
             return None
 
@@ -327,17 +329,4 @@
         """
         return getGradeWeightFromScore(self.score)[1]
-
-    @property
-    def course(self):
-        """Returns the course the ticket is referring to. Returns
-        None if the course has been removed.
-
-        This method is not used in Kofa anymore.
-        """
-        cat = queryUtility(ICatalog, name='courses_catalog')
-        result = cat.searchResults(code=(self.code, self.code))
-        if len(result) != 1:
-            return None
-        return list(result)[0]
 
 CourseTicket = attrs_to_fields(CourseTicket)
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 13002)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_browser.py	(revision 13003)
@@ -1803,8 +1803,4 @@
         self.browser.getControl("Show").click()
         self.assertTrue(self.student_id in self.browser.contents)
-        # The course ticket can be linked with the course.
-        self.assertEqual(
-            self.student['studycourse']['100']['COURSE1'].course,
-            self.course)
         # Lecturer can neither access the student ...
         self.assertRaises(
