Index: main/waeup.kofa/trunk/CHANGES.txt
===================================================================
--- main/waeup.kofa/trunk/CHANGES.txt	(revision 13384)
+++ main/waeup.kofa/trunk/CHANGES.txt	(revision 13394)
@@ -4,4 +4,6 @@
 1.3.4.dev0 (unreleased)
 =======================
+
+* Add portal maintenance mode.
 
 * Allow also reg_number on public page to check application status
Index: main/waeup.kofa/trunk/src/waeup/kofa/applicants/authentication.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/applicants/authentication.py	(revision 13384)
+++ main/waeup.kofa/trunk/src/waeup/kofa/applicants/authentication.py	(revision 13394)
@@ -48,8 +48,13 @@
     def checkPassword(self, password):
         """Check whether the given `password` matches the one stored by
-        students.
-
-        We additionally check if student account has been suspended.
+        students. We additionally check if applicant account has been suspended
+        or if the portal is blocked.
         """
+        try:
+            blocker = grok.getSite()['configuration'].maintmode_enabled_by
+            if blocker:
+                return False
+        except TypeError:  # in unit tests
+            pass
         if not isinstance(password, basestring):
             return False
Index: main/waeup.kofa/trunk/src/waeup/kofa/applicants/browser.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/applicants/browser.py	(revision 13384)
+++ main/waeup.kofa/trunk/src/waeup/kofa/applicants/browser.py	(revision 13394)
@@ -1199,4 +1199,11 @@
         if self.context.expired:
             self.flash(_('Outside application period.'), type='warning')
+            self.redirect(self.url(self.context))
+            return
+        blocker = grok.getSite()['configuration'].maintmode_enabled_by
+        if blocker:
+            self.flash(_('The portal is in maintenance mode '
+                        'and registration temporarily disabled.'),
+                       type='warning')
             self.redirect(self.url(self.context))
             return
Index: main/waeup.kofa/trunk/src/waeup/kofa/applicants/tests/test_browser.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/applicants/tests/test_browser.py	(revision 13384)
+++ main/waeup.kofa/trunk/src/waeup/kofa/applicants/tests/test_browser.py	(revision 13394)
@@ -609,4 +609,24 @@
         self.assertTrue(
             'You logged in.' in self.browser.contents)
+
+    def test_maintenance_mode(self):
+        config = grok.getSite()['configuration']
+        self.login()
+        # Applicant  logged in.
+        self.assertTrue('You logged in' in self.browser.contents)
+        self.assertTrue("Joan None" in self.browser.contents)
+        # If maintenance mode is enabled, applicant is immediately logged out.
+        config.maintmode_enabled_by = u'any_user'
+        self.assertRaises(
+            Unauthorized, self.browser.open, 'http://localhost/app/faculties')
+        self.browser.open('http://localhost/app/login')
+        self.assertTrue('The portal is in maintenance mode' in self.browser.contents)
+        # Applicant really can't login if maintenance mode is enabled.
+        self.login()
+        # A second warning is raised.
+        self.assertTrue(
+            'The portal is in maintenance mode. You can\'t login!'
+            in self.browser.contents)
+        return
 
     def test_applicant_access(self):
@@ -1250,9 +1270,15 @@
 
     def test_register_applicant_create(self):
+        config = grok.getSite()['configuration']
+        config.maintmode_enabled_by = u'any_user'
         self.assertEqual(len(self.app['applicants'][container_name_1]), 1)
         # An applicant can register himself.
         self.browser.open(self.container_path)
         self.browser.getLink("Register for application").click()
-        # Fill the edit form with suitable values
+        self.assertTrue(
+            'The portal is in maintenance mode' in self.browser.contents)
+        config.maintmode_enabled_by = None
+        self.browser.getLink("Register for application").click()
+        # The edit form now opens and can be filled with suitable values
         self.browser.getControl(name="form.firstname").value = 'Anna'
         self.browser.getControl(name="form.lastname").value = 'Kurios'
Index: main/waeup.kofa/trunk/src/waeup/kofa/authentication.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/authentication.py	(revision 13384)
+++ main/waeup.kofa/trunk/src/waeup/kofa/authentication.py	(revision 13394)
@@ -223,4 +223,10 @@
 
     def checkPassword(self, password):
+        try:
+            blocker = grok.getSite()['configuration'].maintmode_enabled_by
+            if blocker and blocker != self.name:
+                return False
+        except (TypeError, KeyError):  # in unit tests
+            pass
         if not isinstance(password, basestring):
             return False
Index: main/waeup.kofa/trunk/src/waeup/kofa/browser/pages.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/browser/pages.py	(revision 13384)
+++ main/waeup.kofa/trunk/src/waeup/kofa/browser/pages.py	(revision 13394)
@@ -337,4 +337,9 @@
 
     def update(self, SUBMIT=None, camefrom=None):
+        blocker = grok.getSite()['configuration'].maintmode_enabled_by
+        if blocker:
+            self.flash(_('The portal is in maintenance mode '
+                        'and authentication (login) temporarily disabled.'),
+                       type='warning')
         self.camefrom = camefrom
         if SUBMIT is not None:
@@ -363,4 +368,11 @@
                     return
                 self.redirect(self.camefrom)
+                return
+            # Display second warning message and remind user that
+            # s/he can't login.
+            if blocker:
+                self.flash(_('The portal is in maintenance mode. '
+                            'You can\'t login!'),
+                           type='danger')
                 return
             # Display appropriate flash message if credentials are correct
@@ -1001,4 +1013,5 @@
     form_fields = grok.AutoFields(IConfigurationContainer).omit(
         'frontpage_dict', 'next_matric_integer', 'next_matric_integer_2')
+    form_fields['maintmode_enabled_by'].for_display = True
 
     @action(_('Save'), style='primary')
@@ -1354,4 +1367,11 @@
 
     def update(self, filename=None, select=None, cancel=None):
+        if not grok.getSite()['configuration'].maintmode_enabled_by and \
+            not self.request.principal.id == 'admin':
+            self.flash(
+                _('Portal must be in maintenance mode for data import.'),
+                type='warning')
+            self.redirect(self.url(self.context))
+            return
         if cancel is not None:
             self.redirect(self.url(self.context))
@@ -1362,4 +1382,5 @@
             session['import_filename'] = select
             self.redirect(self.url(self.context, '@@import2'))
+        return
 
 class DatacenterImportStep2(KofaPage):
@@ -1639,4 +1660,9 @@
             self.mode)
 
+        # Disable maintenance mode if set.
+        if grok.getSite()['configuration'].maintmode_enabled_by:
+            grok.getSite()['configuration'].maintmode_enabled_by = None
+            self.flash(_('Maintenance mode disabled.'))
+
         if self.warn_num:
             self.flash(_('Processing of ${a} rows failed.',
@@ -1823,4 +1849,30 @@
             '%s - downloaded: %s, job_id=%s' % (ob_class, filename, job_id))
         return result
+
+class SwitchMaintModePage(UtilityView, grok.View):
+    """Import managers must disable authentication for all other users
+    before starting an import. This pages switches maintenance mode
+    on and off.
+    """
+    grok.context(IDataCenter)
+    grok.name('switchmaintmode')
+    grok.require('waeup.importData')
+
+    def update(self):
+        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
+        if grok.getSite()['configuration'].maintmode_enabled_by:
+            grok.getSite()['configuration'].maintmode_enabled_by = None
+            self.context.logger.info('%s - maintmode disabled' % ob_class)
+            self.flash(_('Maintenance mode disabled.'))
+        else:
+            grok.getSite()['configuration'].maintmode_enabled_by = unicode(
+                self.request.principal.id)
+            self.context.logger.info('%s - maintmode enabled' % ob_class)
+            self.flash(_('Maintenance mode enabled.'), type='warning')
+        self.redirect(self.url(self.context))
+        return
+
+    def render(self):
+        return
 
 #
Index: main/waeup.kofa/trunk/src/waeup/kofa/browser/templates/configurationmanagepage.pt
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/browser/templates/configurationmanagepage.pt	(revision 13384)
+++ main/waeup.kofa/trunk/src/waeup/kofa/browser/templates/configurationmanagepage.pt	(revision 13394)
@@ -18,4 +18,5 @@
         </tbody>
       </table>
+      <br />
       <div tal:condition="view/availableActions">
         <span tal:repeat="action view/actions" tal:omit-tag="">
Index: main/waeup.kofa/trunk/src/waeup/kofa/browser/tests/test_browser.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/browser/tests/test_browser.py	(revision 13384)
+++ main/waeup.kofa/trunk/src/waeup/kofa/browser/tests/test_browser.py	(revision 13394)
@@ -22,4 +22,5 @@
 import tempfile
 import os
+import grok
 from zc.async.testing import wait_for_result
 from zope.component import createObject, getUtility
@@ -506,5 +507,5 @@
         self.browser.getControl(name="form.password").value = 'secret'
         self.browser.getControl("Login").click()
-        # Yeah, office logged in.
+        # Yeah, officer logged in.
         self.assertMatches(
             '...You logged in...', self.browser.contents)
@@ -531,4 +532,40 @@
         return
 
+    def test_maintenance_mode(self):
+        config = grok.getSite()['configuration']
+        self.app['users'].addUser(
+            'officer', 'secret', title='Bob Officer', email='bob@abcd.ng')
+        self.browser.open('http://localhost/app/login')
+        self.browser.getControl(name="form.login").value = 'officer'
+        self.browser.getControl(name="form.password").value = 'secret'
+        self.browser.getControl("Login").click()
+        # Officer logged in.
+        self.assertMatches('...You logged in...', self.browser.contents)
+        self.assertTrue("Bob Officer" in self.browser.contents)
+        # If maintenance mode is enabled, officer is immediately logged out.
+        config.maintmode_enabled_by = u'any_user'
+        self.assertRaises(
+            Unauthorized, self.browser.open, 'http://localhost/app/faculties')
+        self.browser.open('http://localhost/app/login')
+        self.assertMatches(
+            '...The portal is in maintenance mode...',
+            self.browser.contents)
+        # Officers really can't login if maintenance mode is enabled.
+        self.browser.getControl(name="form.login").value = 'officer'
+        self.browser.getControl(name="form.password").value = 'secret'
+        self.browser.getControl("Login").click()
+        # A second warning is raised.
+        self.assertMatches(
+            '...The portal is in maintenance mode. You can\'t login!...',
+            self.browser.contents)
+        # Offficer can login if s/he is the blocker.
+        config.maintmode_enabled_by = u'officer'
+        self.browser.getControl(name="form.login").value = 'officer'
+        self.browser.getControl(name="form.password").value = 'secret'
+        self.browser.getControl("Login").click()
+        self.assertTrue('You logged in' in self.browser.contents)
+        self.assertTrue('Logout' in self.browser.contents)
+        return
+
     def test_sources_overview(self):
         self.browser.open('http://localhost/app/sources')
Index: main/waeup.kofa/trunk/src/waeup/kofa/browser/viewlets.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/browser/viewlets.py	(revision 13384)
+++ main/waeup.kofa/trunk/src/waeup/kofa/browser/viewlets.py	(revision 13394)
@@ -628,4 +628,27 @@
     text = _('View processed files')
 
+class SwitchMaintMode(ActionButton):
+    grok.context(IDataCenter)
+    grok.require('waeup.importData')
+    grok.view(DatacenterPage)
+    grok.order(7)
+    icon = 'actionicon_stop.png'
+    target = 'switchmaintmode'
+    text = _('Switch maintenance mode')
+
+    @property
+    def onclick(self):
+        if grok.getSite()['configuration'].maintmode_enabled_by:
+            return
+        return "return window.confirm(%s);" % _(
+            "'In maintenance mode no other user can login, and "
+            "already logged-in users will be automatically logged out. "
+            "You will be the only logged-in user and you can safely start "
+            "any import. Please wait a few seconds before starting the import "
+            "so that all running processes are finished. "
+            "If the import is done, maintenance mode will "
+            "be automatically disabled. \\n\\n"
+            "You really want to enable maintenance mode?'")
+
 #
 # Primary navigation tabs (in upper left navigation bar)...
Index: main/waeup.kofa/trunk/src/waeup/kofa/doctests/batchprocessing_browser.txt
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/doctests/batchprocessing_browser.txt	(revision 13384)
+++ main/waeup.kofa/trunk/src/waeup/kofa/doctests/batchprocessing_browser.txt	(revision 13394)
@@ -86,4 +86,5 @@
 Step 1: start batch processing:
 
+    >>> browser.getLink('Switch maintenance mode').click()
     >>> browser.getLink('Process data').click()
     >>> button = lookup_submit_value(
@@ -191,4 +192,5 @@
 Step 1: start batch processing:
 
+    >>> browser.getLink('Switch maintenance mode').click()
     >>> browser.getLink('Process data').click()
     >>> button = lookup_submit_value(
@@ -256,4 +258,5 @@
 Step 1: start batch processing:
 
+    >>> browser.getLink('Switch maintenance mode').click()
     >>> browser.getLink('Process data').click()
     >>> button = lookup_submit_value(
@@ -321,4 +324,5 @@
 Step 1: start batch processing:
 
+    >>> browser.getLink('Switch maintenance mode').click()
     >>> browser.getLink('Process data').click()
     >>> button = lookup_submit_value(
@@ -387,4 +391,5 @@
 Step 1: start batch processing:
 
+    >>> browser.getLink('Switch maintenance mode').click()
     >>> browser.getLink('Process data').click()
     >>> button = lookup_submit_value(
@@ -453,4 +458,5 @@
 Step 1: start batch processing:
 
+    >>> browser.getLink('Switch maintenance mode').click()
     >>> browser.getLink('Process data').click()
     >>> button = lookup_submit_value(
@@ -539,4 +545,5 @@
 Step 1: start batch processing:
 
+    >>> browser.getLink('Switch maintenance mode').click()
     >>> browser.getLink('Process data').click()
     >>> button = lookup_submit_value(
@@ -602,4 +609,5 @@
 
     >>> browser.open('http://localhost/app/datacenter')
+    >>> browser.getLink('Switch maintenance mode').click()
     >>> browser.getLink('Process data').click()
     >>> button = lookup_submit_value(
Index: main/waeup.kofa/trunk/src/waeup/kofa/interfaces.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/interfaces.py	(revision 13384)
+++ main/waeup.kofa/trunk/src/waeup/kofa/interfaces.py	(revision 13394)
@@ -741,4 +741,9 @@
         )
 
+    maintmode_enabled_by = schema.TextLine(
+        title = _(u'Maintenance Mode enabled by'),
+        default = None,
+        required = False,
+        )
 
     def addSessionConfiguration(sessionconfiguration):
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/authentication.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/authentication.py	(revision 13384)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/authentication.py	(revision 13394)
@@ -113,7 +113,13 @@
         """Check whether the given `password` matches the one stored by
         students or the temporary password set by officers.
-
-        We additionally check if student account has been suspended.
-        """
+        We additionally check if student account has been suspended
+        or if the portal is blocked.
+        """
+        try:
+            blocker = grok.getSite()['configuration'].maintmode_enabled_by
+            if blocker:
+                return False
+        except TypeError:  # in unit tests
+            pass
         if not isinstance(password, basestring):
             return 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 13384)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_browser.py	(revision 13394)
@@ -1387,4 +1387,5 @@
         filewidget.add_file(filecontents, 'text/plain', 'students.csv')
         self.browser.getControl(name='SUBMIT').click()
+        self.browser.getLink("Switch maintenance mode").click()
         self.browser.getLink('Process data').click()
         button = lookup_submit_value(
@@ -1413,4 +1414,6 @@
         filewidget.add_file(filecontents, 'text/plain', 'studycourses.csv')
         self.browser.getControl(name='SUBMIT').click()
+        # Meanwhile maintenance mode is disabled again.
+        self.browser.getLink("Switch maintenance mode").click()
         self.browser.getLink('Process data').click()
         button = lookup_submit_value(
@@ -2376,4 +2379,30 @@
             '...You logged in...', self.browser.contents)
 
+    def test_maintenance_mode(self):
+        config = grok.getSite()['configuration']
+        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()
+        # Student logged in.
+        self.assertTrue('You logged in' in self.browser.contents)
+        self.assertTrue("Anna Tester" in self.browser.contents)
+        # If maintenance mode is enabled, student is immediately logged out.
+        config.maintmode_enabled_by = u'any_user'
+        self.assertRaises(
+            Unauthorized, self.browser.open, 'http://localhost/app/faculties')
+        self.browser.open('http://localhost/app/login')
+        self.assertTrue('The portal is in maintenance mode' in self.browser.contents)
+        # Student really can't login if maintenance mode is enabled.
+        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()
+        # A second warning is raised.
+        self.assertTrue(
+            'The portal is in maintenance mode. You can\'t login!'
+            in self.browser.contents)
+        return
+
     def test_student_clearance(self):
         # Student cant login if their password is not set
Index: main/waeup.kofa/trunk/src/waeup/kofa/university/tests/test_batching.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/university/tests/test_batching.py	(revision 13384)
+++ main/waeup.kofa/trunk/src/waeup/kofa/university/tests/test_batching.py	(revision 13394)
@@ -351,4 +351,12 @@
         self.browser.getControl('Upload').click()
         self.browser.getLink('Process data').click()
+        self.assertTrue('Portal must be in maintenance mode for data import.'
+            in self.browser.contents)
+        self.browser.getLink("Switch").click()
+        self.assertTrue('Maintenance mode enabled' in self.browser.contents)
+        # Maintenance mode is now set.
+        self.assertEqual(
+            self.app['configuration'].maintmode_enabled_by, 'zope.mgr')
+        self.browser.getLink('Process data').click()
         self.browser.getControl(name="select").click()
         importerselect = self.browser.getControl(name='importer')
@@ -360,4 +368,8 @@
         self.browser.getControl('Perform import').click()
         self.assertTrue('Successfully processed 1 rows' in self.browser.contents)
+        self.assertTrue('Maintenance mode disabled' in self.browser.contents)
+        # Maintenance mode is really disabled.
+        self.assertEqual(
+            self.app['configuration'].maintmode_enabled_by, None)
         # We import the same file a second time.
         self.browser.open(self.datacenter_path)
