Index: main/waeup.kofa/trunk/CHANGES.txt
===================================================================
--- main/waeup.kofa/trunk/CHANGES.txt	(revision 15405)
+++ main/waeup.kofa/trunk/CHANGES.txt	(revision 15416)
@@ -5,11 +5,17 @@
 =======================
 
+* Backup deleted graduated student data somewhere else to ease graduated
+  student data migration.
+
 * Add `transcript_remark` field to study levels. Add page to allow
   transcript officers editing the field during transcript processing.
 
 * Increase password strength. Officers are now required
-  to set a password which has at least 8 characters, contains
+  to set a password which has at least 8 characters and which contains
   at least one uppercase letter, one lowercase letter and one
-  digit.
+  digit. Stored insecure passwords are no longer accepted.
+  Officers with an insecure password can't login and are
+  redirected to the `ChangePasswordRequestPage` to request a
+  new password.
 
 * Add `AccommodationPaymentsExporter` which can be used by
Index: main/waeup.kofa/trunk/docs/source/userdocs/students/browser.rst
===================================================================
--- main/waeup.kofa/trunk/docs/source/userdocs/students/browser.rst	(revision 15405)
+++ main/waeup.kofa/trunk/docs/source/userdocs/students/browser.rst	(revision 15416)
@@ -310,5 +310,5 @@
 pressing the 'Update scores from table' button below. Scores can be
 cleared by removing the respective values. Lecturers have to be
-online during this process.(2) Alternatively, lecturers can download
+online during this process. (2) Alternatively, lecturers can download
 a csv file, edit scores in this csv file offline and upload the same
 file when they are online again. This procedure is explained in
Index: main/waeup.kofa/trunk/src/waeup/kofa/datacenter.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/datacenter.py	(revision 15405)
+++ main/waeup.kofa/trunk/src/waeup/kofa/datacenter.py	(revision 15416)
@@ -58,5 +58,5 @@
         """Create standard subdirs.
         """
-        for name in ['finished', 'unfinished', 'logs', 'deleted']:
+        for name in ['finished', 'graduated', 'unfinished', 'logs', 'deleted']:
             path = os.path.join(self.storage, name)
             if os.path.exists(path):
@@ -67,7 +67,13 @@
     @property
     def deleted_path(self):
-        """Get the path for deleted object data.
+        """Get the path for deleted student data.
         """
         return os.path.join(self.storage, 'deleted')
+
+    @property
+    def graduated_path(self):
+        """Get the path for deleted graduated student data.
+        """
+        return os.path.join(self.storage, 'graduated')
 
     def getPendingFiles(self, sort='name'):
Index: main/waeup.kofa/trunk/src/waeup/kofa/doctests/batchprocessing_browser.txt
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/doctests/batchprocessing_browser.txt	(revision 15405)
+++ main/waeup.kofa/trunk/src/waeup/kofa/doctests/batchprocessing_browser.txt	(revision 15416)
@@ -644,5 +644,5 @@
 
     >>> sorted(os.listdir(dc_path))
-    ['deleted', 'finished', 'logs', 'unfinished', 'users_zope.mgr.create.pending.csv']
+    ['deleted', 'finished', 'graduated', 'logs', 'unfinished', 'users_zope.mgr.create.pending.csv']
 
     >>> os.listdir(dc_path + '/unfinished')
Index: main/waeup.kofa/trunk/src/waeup/kofa/doctests/datacenter.txt
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/doctests/datacenter.txt	(revision 15405)
+++ main/waeup.kofa/trunk/src/waeup/kofa/doctests/datacenter.txt	(revision 15416)
@@ -23,5 +23,5 @@
     '/tmp/tmp...'
 
-Beside other things it provides a location to put data of deleted
+Beside other things it provides two locations to put data of deleted
 items into:
 
@@ -29,4 +29,7 @@
     >>> del_path = mydatacenter.deleted_path
     >>> os.path.isdir(del_path)
+    True
+    >>> grad_path = mydatacenter.graduated_path
+    >>> os.path.isdir(grad_path)
     True
 
@@ -163,5 +166,5 @@
     ...                            pending_src, mode='create')
     >>> sorted(os.listdir(dc_root))
-    ['deleted', 'finished', 'logs', 'unfinished']
+    ['deleted', 'finished', 'graduated', 'logs', 'unfinished']
 
     >>> sorted(os.listdir(fin_dir))
@@ -188,5 +191,5 @@
     ...                                 pending_src, mode='create')
     >>> sorted(os.listdir(dc_root))
-    ['deleted', 'finished', 'logs', 'mysource.create.pending.csv', 'unfinished']
+    ['deleted', 'finished', 'graduated', 'logs', 'mysource.create.pending.csv', 'unfinished']
 
     >>> sorted(os.listdir(fin_dir))
@@ -209,5 +212,5 @@
 
     >>> sorted(os.listdir(dc_root))
-    ['deleted', 'finished', 'logs', 'unfinished']
+    ['deleted', 'finished', 'graduated', 'logs', 'unfinished']
 
     >>> sorted(os.listdir(fin_dir))
@@ -245,5 +248,5 @@
 
     >>> sorted(os.listdir(dc_root))
-    ['deleted', 'finished', 'logs', 'mysource.create.pending.csv', 'unfinished']
+    ['deleted', 'finished', 'graduated', 'logs', 'mysource.create.pending.csv', 'unfinished']
 
     >>> sorted(os.listdir(fin_dir))
@@ -261,5 +264,5 @@
 
     >>> sorted(os.listdir(dc_root))
-    ['deleted', 'finished', 'logs', 'unfinished']
+    ['deleted', 'finished', 'graduated', 'logs', 'unfinished']
 
     >>> sorted(os.listdir(fin_dir))
Index: main/waeup.kofa/trunk/src/waeup/kofa/doctests/pages.txt
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/doctests/pages.txt	(revision 15405)
+++ main/waeup.kofa/trunk/src/waeup/kofa/doctests/pages.txt	(revision 15416)
@@ -1220,5 +1220,5 @@
 
   >>> sorted(os.listdir(uploadpath))
-  ['deleted', 'finished', 'logs', 'unfinished']
+  ['deleted', 'finished', 'graduated', 'logs', 'unfinished']
 
 
@@ -1261,5 +1261,5 @@
 
   >>> sorted(os.listdir(uploadpath))
-  ['deleted', 'finished', 'logs', 'myfaculties_zope.mgr.csv', 'unfinished']
+  ['deleted', 'finished', 'graduated', 'logs', 'myfaculties_zope.mgr.csv', 'unfinished']
 
 We create and upload also a CSV file containing departments:
Index: main/waeup.kofa/trunk/src/waeup/kofa/interfaces.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/interfaces.py	(revision 15405)
+++ main/waeup.kofa/trunk/src/waeup/kofa/interfaces.py	(revision 15416)
@@ -929,5 +929,9 @@
 
     deleted_path = schema.Bytes(
-        title = u'Path were data about deleted objects should be stored.'
+        title = u'Path for deleted student data.'
+        )
+
+    graduated_path = schema.Bytes(
+        title = u'Path for deleted graduated student data.'
         )
 
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/batching.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/batching.py	(revision 15405)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/batching.py	(revision 15416)
@@ -652,5 +652,5 @@
     `ticket_session` is an additional attribute which can be used to store the 
     session of the course when it was taken. Usually this information is 
-    redundantbecause the the parent study level object already contains this 
+    redundant because the the parent study level object already contains this
     information except for the study level zero container which can be used to
     store 'orphaned' course tickets.
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/student.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/student.py	(revision 15405)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/student.py	(revision 15416)
@@ -486,4 +486,7 @@
     del_dir = site['datacenter'].deleted_path
 
+    if student.state == GRADUATED:
+        del_dir = site['datacenter'].graduated_path
+
     # save files of the student
     move_student_files(student, del_dir)
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_student.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_student.py	(revision 15405)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_student.py	(revision 15416)
@@ -30,4 +30,5 @@
 from zope.interface import verify
 from zope.schema.interfaces import RequiredMissing
+from hurry.workflow.interfaces import IWorkflowState
 from waeup.kofa.interfaces import IExtFileStore, IFileStoreNameChooser
 from waeup.kofa.students.student import (
@@ -180,5 +181,4 @@
             del_dir, 'media', 'students', '00110', 'A111111',
             'passport_A111111.jpg')
-
         # The image was copied over
         self.assertTrue(os.path.isfile(del_img_path))
@@ -186,9 +186,35 @@
             open(del_img_path, 'rb').read(),
             self.image_contents)
-
         # The student data were put into CSV files
         STUDENT_BACKUP_EXPORTER_NAMES = getUtility(
             IStudentsUtils).STUDENT_BACKUP_EXPORTER_NAMES
-
+        for name in STUDENT_BACKUP_EXPORTER_NAMES:
+            csv_path = os.path.join(del_dir, '%s.csv' % name)
+            self.assertTrue(os.path.isfile(csv_path))
+            contents = open(csv_path, 'rb').read().split('\r\n')
+            # We expect 3 lines output including a linebreak at end of file.
+            self.assertEqual(len(contents), 3)
+        return
+
+    def test_backup_single_graduated_data(self):
+        # when a single graduated student is removed, the data is backed up
+        # somewhere else
+        self.setup_student(self.student)
+        IWorkflowState(self.student).setState('graduated')
+        # Add a fake image
+        self.create_passport_img(self.student)
+        handle_student_removed(self.student, None)
+        del_dir = self.app['datacenter'].graduated_path
+        del_img_path = os.path.join(
+            del_dir, 'media', 'students', '00110', 'A111111',
+            'passport_A111111.jpg')
+        # The image was copied over
+        self.assertTrue(os.path.isfile(del_img_path))
+        self.assertEqual(
+            open(del_img_path, 'rb').read(),
+            self.image_contents)
+        # The student data were put into CSV files
+        STUDENT_BACKUP_EXPORTER_NAMES = getUtility(
+            IStudentsUtils).STUDENT_BACKUP_EXPORTER_NAMES
         for name in STUDENT_BACKUP_EXPORTER_NAMES:
             csv_path = os.path.join(del_dir, '%s.csv' % name)
