Index: main/waeup.kofa/trunk/CHANGES.txt
===================================================================
--- main/waeup.kofa/trunk/CHANGES.txt	(revision 13030)
+++ main/waeup.kofa/trunk/CHANGES.txt	(revision 13031)
@@ -4,4 +4,7 @@
 1.3.2.dev0 (unreleased)
 =======================
+
+* Implement session-specific course registration deadline and
+  late registration payment.
 
 * Remove quite old bug in `doImport`: Replace empty strings *and* lists with
Index: main/waeup.kofa/trunk/docs/source/userdocs/students/browser.rst
===================================================================
--- main/waeup.kofa/trunk/docs/source/userdocs/students/browser.rst	(revision 13030)
+++ main/waeup.kofa/trunk/docs/source/userdocs/students/browser.rst	(revision 13031)
@@ -113,4 +113,7 @@
 =====================
 
+Register Courses
+================
+
 Using Activation Codes
 ======================
Index: main/waeup.kofa/trunk/docs/source/userdocs/students/interfaces.rst
===================================================================
--- main/waeup.kofa/trunk/docs/source/userdocs/students/interfaces.rst	(revision 13030)
+++ main/waeup.kofa/trunk/docs/source/userdocs/students/interfaces.rst	(revision 13031)
@@ -264,4 +264,9 @@
 used for persistent storage of same-named attributes in the database.
 
+The property attribute `course_registration_allowed` determines
+whether course registration has ended. In the base package students
+can proceed if they have paid late registration fee. The conditions
+for course registration can be configured in custom packages.
+
 `ICourseTicket`
 ---------------
Index: main/waeup.kofa/trunk/src/waeup/kofa/applicants/interfaces.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/applicants/interfaces.py	(revision 13030)
+++ main/waeup.kofa/trunk/src/waeup/kofa/applicants/interfaces.py	(revision 13031)
@@ -252,5 +252,5 @@
         title = _(u'Application Start Date'),
         required = False,
-        description = _('Example:') + u'2011-12-01 18:30:00+01:00',
+        description = _('Example: ') + u'2011-12-01 18:30:00+01:00',
         )
 
@@ -258,5 +258,5 @@
         title = _(u'Application Closing Date'),
         required = False,
-        description = _('Example:') + u'2011-12-31 23:59:59+01:00',
+        description = _('Example: ') + u'2011-12-31 23:59:59+01:00',
         )
 
Index: main/waeup.kofa/trunk/src/waeup/kofa/interfaces.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/interfaces.py	(revision 13030)
+++ main/waeup.kofa/trunk/src/waeup/kofa/interfaces.py	(revision 13031)
@@ -696,10 +696,4 @@
         )
 
-    current_coursereg_deadline = schema.Datetime(
-        title = _(u'Current Course Registration Deadline'),
-        required = False,
-        description = _('Example: ') + u'2011-12-31 23:59:59+01:00',
-        )
-
     current_academic_session = schema.Choice(
         title = _(u'Current Academic Session'),
@@ -762,4 +756,10 @@
         )
 
+    late_registration_fee = schema.Float(
+        title = _(u'Late Course Registration Fee'),
+        default = 0.0,
+        required = False,
+        )
+
     clearance_enabled = schema.Bool(
         title = _(u'Clearance enabled'),
@@ -774,4 +774,10 @@
         required = False,
         default = [],
+        )
+
+    coursereg_deadline = schema.Datetime(
+        title = _(u'Current Course Registration Deadline'),
+        required = False,
+        description = _('Example: ') + u'2011-12-31 23:59:59+01:00',
         )
 
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/browser.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/browser.py	(revision 13030)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/browser.py	(revision 13031)
@@ -2682,4 +2682,10 @@
                 mapping = {'a':max_credits}), type="warning")
             return
+        if not self.context.course_registration_allowed:
+            self.flash(_(
+                "Course registration has ended. "
+                "Please pay the late registration fee."), type="warning")
+            #self.redirect(self.url(self.context))
+            return
         IWorkflowInfo(self.context.student).fireTransition(
             'register_courses')
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/interfaces.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/interfaces.py	(revision 13030)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/interfaces.py	(revision 13031)
@@ -578,4 +578,5 @@
     gpa_params = Attribute('GPA parameters for this level.')
     cumulative_params = Attribute('Cumulative GPA and other cumulative parameters for this level')
+    course_registration_allowed = Attribute('True if registerin courses is allowed')
 
     level = schema.Choice(
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/studylevel.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/studylevel.py	(revision 13030)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/studylevel.py	(revision 13031)
@@ -21,4 +21,6 @@
 """
 import grok
+import pytz
+from datetime import datetime
 from zope.component.interfaces import IFactory
 from zope.catalog.interfaces import ICatalog
@@ -205,4 +207,22 @@
         return studylevelsource.factory.getTitle(self.__parent__, self.level)
 
+    @property
+    def course_registration_allowed(self):
+        try:
+            deadline = grok.getSite()['configuration'][
+                    str(self.level_session)].coursereg_deadline
+        except TypeError, KeyError:
+            return True
+        payment_made = False
+        if len(self.student['payments']):
+            for ticket in self.student['payments'].values():
+                if ticket.p_category == 'late_registration' and \
+                    ticket.p_session == self.level_session and \
+                    ticket.p_state == 'paid':
+                        payment_made = True
+        if deadline and deadline < datetime.now(pytz.utc) and not payment_made:
+            return False
+        return True
+
     def addCourseTicket(self, ticket, course):
         """Add a course ticket object.
@@ -294,6 +314,8 @@
     def editable_by_lecturer(self):
         try:
-            cas = grok.getSite()['configuration'].current_academic_session
-            if self.student.state == VALIDATED and self.student.current_session == cas:
+            cas = grok.getSite()[
+                'configuration'].current_academic_session
+            if self.student.state == VALIDATED and \
+                self.student.current_session == cas:
                 return True
         except (AttributeError, TypeError): # in unit tests
@@ -302,5 +324,6 @@
 
     def writeLogMessage(self, view, message):
-        return self.__parent__.__parent__.__parent__.writeLogMessage(view, message)
+        return self.__parent__.__parent__.__parent__.writeLogMessage(
+            view, message)
 
     @property
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 13030)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_browser.py	(revision 13031)
@@ -194,4 +194,5 @@
         configuration.booking_fee = 123.4
         configuration.maint_fee = 987.0
+        configuration.late_registration_fee = 345.0
         self.app['configuration'].addSessionConfiguration(configuration)
 
@@ -3179,4 +3180,38 @@
             in logcontent)
 
+    def test_late_registration(self):
+        # Login
+        delta = timedelta(days=10)
+        self.app['configuration'][
+            '2004'].coursereg_deadline = datetime.now(pytz.utc) - delta
+        IWorkflowState(self.student).setState('school fee paid')
+        self.browser.open(self.login_path)
+        self.browser.getControl(name="form.login").value = self.student_id
+        self.browser.getControl(name="form.password").value = 'spwd'
+        self.browser.getControl("Login").click()
+        self.browser.open(self.payments_path)
+        self.browser.open(self.payments_path + '/addop')
+        self.browser.getControl(name="form.p_category").value = ['late_registration']
+        self.browser.getControl("Create ticket").click()
+        self.assertMatches('...ticket created...',
+                           self.browser.contents)
+        self.browser.open(self.payments_path)
+        ctrl = self.browser.getControl(name='val_id')
+        value = ctrl.options[0]
+        self.browser.getLink("Study Course").click()
+        self.browser.getLink("Add course list").click()
+        self.assertMatches('...Add current level 100 (Year 1)...',
+                           self.browser.contents)
+        self.browser.getControl("Create course list now").click()
+        self.browser.getLink("100").click()
+        self.browser.getLink("Edit course list").click()
+        self.browser.getControl("Register course list").click()
+        self.assertTrue('Course registration has ended. Please pay' in self.browser.contents)
+        self.student['payments'][value].approve()
+        self.browser.getControl("Register course list").click()
+        self.assertTrue('Course list has been registered' in self.browser.contents)
+        self.assertEqual(self.student.state, 'courses registered')
+
+
 class StudentRequestPWTests(StudentsFullSetup):
     # Tests for student registration
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/utils.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/utils.py	(revision 13030)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/utils.py	(revision 13031)
@@ -471,4 +471,6 @@
         elif category == 'transcript':
             amount = academic_session.transcript_fee
+        elif category == 'late_registration':
+            amount = academic_session.late_registration_fee
         if amount in (0.0, None):
             return _('Amount could not be determined.'), None
Index: main/waeup.kofa/trunk/src/waeup/kofa/utils/utils.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/utils/utils.py	(revision 13030)
+++ main/waeup.kofa/trunk/src/waeup/kofa/utils/utils.py	(revision 13031)
@@ -175,4 +175,5 @@
         'application': 'Application Fee',
         'transcript': 'Transcript Fee',
+        'late_registration': 'Late Course Registration Fee'
         }
 
