Index: main/waeup.kofa/trunk/src/waeup/kofa/students/browser.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/browser.py	(revision 15606)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/browser.py	(revision 15609)
@@ -50,5 +50,5 @@
     academic_sessions_vocab, IDataCenter, DOCLINK)
 from waeup.kofa.interfaces import MessageFactory as _
-from waeup.kofa.mandates.mandate import PasswordMandate
+from waeup.kofa.mandates.mandate import PasswordMandate, ParentsPasswordMandate
 from waeup.kofa.university.interfaces import (
     IDepartment, ICertificate, ICourse)
@@ -392,4 +392,10 @@
     grok.template('loginasstep1')
     pnav = 4
+
+    def update(self):
+        super(LoginAsStudentStep1, self).update()
+        kofa_utils = getUtility(IKofaUtils)
+        self.temp_password_minutes = kofa_utils.TEMP_PASSWORD_MINUTES
+        return
 
     def label(self):
@@ -2555,5 +2561,5 @@
     grok.require('waeup.handleStudent')
     form_fields = grok.AutoFields(IStudentBase).select(
-        'email', 'phone')
+        'email', 'phone', 'parents_email')
     label = _('Edit base data')
     pnav = 4
@@ -3216,4 +3222,80 @@
         return
 
+class ParentsUser:
+    pass
+
+class RequestParentsPasswordPage(StudentRequestPasswordPage):
+    """Captcha'd request password page for parents.
+    """
+    grok.name('requestppw')
+    grok.template('requestppw')
+    label = _('Request password for parents access')
+
+    def update(self):
+        super(RequestParentsPasswordPage, self).update()
+        kofa_utils = getUtility(IKofaUtils)
+        self.temp_password_minutes = kofa_utils.TEMP_PASSWORD_MINUTES
+        return
+
+    @action(_('Send temporary login credentials to email address'), style='primary')
+    def get_credentials(self, **data):
+        if not self.captcha_result.is_valid:
+            # Captcha will display error messages automatically.
+            # No need to flash something.
+            return
+        number = data.get('number','')
+        lastname = data.get('lastname','')
+        email = data['email']
+        cat = getUtility(ICatalog, name='students_catalog')
+        results = list(
+            cat.searchResults(reg_number=(number, number)))
+        if not results:
+            results = list(
+                cat.searchResults(matric_number=(number, number)))
+        if results:
+            student = results[0]
+            if getattr(student,'lastname',None) is None:
+                self.flash(_('An error occurred.'), type="danger")
+                return
+            elif student.lastname.lower() != lastname.lower():
+                # Don't tell the truth here. Anonymous must not
+                # know that a record was found and only the lastname
+                # verification failed.
+                self.flash(_('No student record found.'), type="warning")
+                return
+            elif email != student.parents_email:
+                self.flash(_('Wrong email address.'), type="warning")
+                return
+        else:
+            self._redirect_no_student()
+            return
+        kofa_utils = getUtility(IKofaUtils)
+        password = kofa_utils.genPassword()
+        mandate = ParentsPasswordMandate()
+        mandate.params['password'] = password
+        mandate.params['student'] = student
+        site = grok.getSite()
+        site['mandates'].addMandate(mandate)
+        # Send email with credentials
+        args = {'mandate_id':mandate.mandate_id}
+        mandate_url = self.url(site) + '/mandate?%s' % urlencode(args)
+        url_info = u'Confirmation link: %s' % mandate_url
+        msg = _('You have successfully requested a parents password for the')
+        # Create a fake user
+        user = ParentsUser()
+        user.name = student.student_id
+        user.title = "Parents of %s" % student.display_fullname
+        user.email = student.parents_email
+        if kofa_utils.sendCredentials(user, password, url_info, msg):
+            email_sent = user.email
+        else:
+            email_sent = None
+        self._redirect(email=email_sent, password=password,
+            student_id=student.student_id)
+        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
+        self.context.logger.info(
+            '%s - %s (%s) - %s' % (ob_class, number, student.student_id, email_sent))
+        return
+
 class StudentRequestPasswordEmailSent(KofaPage):
     """Landing page after successful password request.
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/browser_templates/loginasstep1.pt
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/browser_templates/loginasstep1.pt	(revision 15606)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/browser_templates/loginasstep1.pt	(revision 15609)
@@ -5,5 +5,5 @@
     for
     <span i18n:name="minutes"
-          tal:replace=view/context/temp_password_minutes></span>
+          tal:replace=view/temp_password_minutes></span>
     minutes. The student can't login
     during this time period. After expiration the student can login
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/interfaces.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/interfaces.py	(revision 15606)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/interfaces.py	(revision 15609)
@@ -306,4 +306,10 @@
         )
 
+    parents_email = schema.ASCIILine(
+        title = _(u"Parents' Email"),
+        required = False,
+        constraint=validate_email,
+        )
+
     def setTempPassword(user, password):
         """Set a temporary password (LDAP-compatible) SSHA encoded for
@@ -427,9 +433,4 @@
     number = schema.TextLine(
         title = _(u'Registr. or Matric. Number'),
-        required = True,
-        )
-
-    firstname = schema.TextLine(
-        title = _(u'First Name'),
         required = True,
         )
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/student.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/student.py	(revision 15606)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/student.py	(revision 15609)
@@ -58,6 +58,4 @@
     grok.provides(IStudent)
 
-    temp_password_minutes = 10
-
     def __init__(self):
         super(Student, self).__init__()
@@ -91,6 +89,8 @@
         """
         temp_password_dict = getattr(self, 'temp_password', None)
+        kofa_utils = getUtility(IKofaUtils)
+        temp_password_minutes =  kofa_utils.TEMP_PASSWORD_MINUTES
         if temp_password_dict is not None:
-            delta = timedelta(minutes=self.temp_password_minutes)
+            delta = timedelta(minutes=temp_password_minutes)
             now = datetime.utcnow()
             if now < temp_password_dict.get('timestamp') + delta:
@@ -120,6 +120,8 @@
         """
         parents_password_dict = getattr(self, 'parents_password', None)
+        kofa_utils = getUtility(IKofaUtils)
+        temp_password_minutes =  kofa_utils.TEMP_PASSWORD_MINUTES
         if parents_password_dict is not None:
-            delta = timedelta(minutes=self.temp_password_minutes)
+            delta = timedelta(minutes=temp_password_minutes)
             now = datetime.utcnow()
             if now < parents_password_dict.get('timestamp') + delta:
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 15606)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_browser.py	(revision 15609)
@@ -3783,9 +3783,10 @@
             'adm_code,clr_code,date_of_birth,email,employer,'
             'firstname,flash_notice,lastname,matric_number,middlename,nationality,'
-            'officer_comment,perm_address,personal_updated,phone,reg_number,'
+            'officer_comment,parents_email,perm_address,'
+            'personal_updated,phone,reg_number,'
             'sex,student_id,suspended,suspended_comment,'
             'password,state,history,certcode,is_postgrad,'
             'current_level,current_session\r\n'
-            ',,1981-02-04#,aa@aa.ng,,Anna,,Tester,234,,,,,,'
+            ',,1981-02-04#,aa@aa.ng,,Anna,,Tester,234,,,,,,,'
             '1234#,123,m,K1000000,0,,{SSHA}' in self.browser.contents)
         self.browser.open('http://localhost/app/datacenter/@@export')
@@ -3911,9 +3912,10 @@
             'adm_code,clr_code,date_of_birth,email,employer,'
             'firstname,flash_notice,lastname,matric_number,middlename,nationality,'
-            'officer_comment,perm_address,personal_updated,phone,reg_number,'
+            'officer_comment,parents_email,perm_address,'
+            'personal_updated,phone,reg_number,'
             'sex,student_id,suspended,suspended_comment,'
             'password,state,history,certcode,is_postgrad,'
             'current_level,current_session\r\n'
-            ',,1981-02-04#,aa@aa.ng,,Anna,,Tester,234,,,,,,'
+            ',,1981-02-04#,aa@aa.ng,,Anna,,Tester,234,,,,,,,'
             '1234#,123,m,K1000000,0,,{SSHA}' in self.browser.contents)
         self.assertEqual(len(self.app['datacenter'].running_exports), 1)
@@ -4675,6 +4677,22 @@
     # Tests for UI actions when acting as parents.
 
+    def test_request_ppw(self):
+        self.app['students'][self.student_id].parents_email = 'par@yy.zz'
+        self.browser.open('http://localhost/app/requestppw')
+        self.browser.getControl(name="form.lastname").value = 'tESTer'
+        self.browser.getControl(name="form.number").value = '123'
+        self.browser.getControl(name="form.email").value = 'par@yy.zz'
+        self.browser.getControl("Send temporary login credentials").click()
+        self.assertTrue('Your password request was successful.'
+            in self.browser.contents)
+        logfile = os.path.join(
+            self.app['datacenter'].storage, 'logs', 'main.log')
+        logcontent = open(logfile).read()
+        self.assertTrue('zope.anybody - students.browser.RequestParentsPasswordPage - '
+                        '123 (K1000000) - par@yy.zz' in logcontent)
+        return
+
     def test_login_as_parents(self):
-        # Sstudent login still works after all the changes made
+        # Student login still works after all the changes made
         self.browser.open(self.login_path)
         self.browser.getControl(name="form.login").value = self.student_id
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_export.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_export.py	(revision 15606)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_export.py	(revision 15609)
@@ -126,5 +126,5 @@
     std_csv_entry = (
         'my adm code,my clr code,1981-02-04#,anna@sample.com,,'
-        'Anna,,Tester,234,M.,NG,,"Studentroad 21\nLagos 123456\n",,'
+        'Anna,,Tester,234,M.,NG,,,"Studentroad 21\nLagos 123456\n",,'
         '+234-123-12345#,123,f,A111111,0,,,created'
         )
@@ -158,10 +158,11 @@
             'adm_code,clr_code,date_of_birth,email,'
             'employer,firstname,flash_notice,lastname,matric_number,middlename,'
-            'nationality,officer_comment,perm_address,personal_updated,'
+            'nationality,officer_comment,parents_email,'
+            'perm_address,personal_updated,'
             'phone,reg_number,sex,student_id,suspended,suspended_comment,'
             'password,state,history,'
             'certcode,is_postgrad,current_level,current_session\r\n'
             'my adm code,my clr code,'
-            '1981-02-04#,anna@sample.com,,Anna,,Tester,234,M.,NG,,'
+            '1981-02-04#,anna@sample.com,,Anna,,Tester,234,M.,NG,,,'
             '"Studentroad 21\nLagos 123456\n",,+234-123-12345#,123,f,'
             'A111111,0,,,created'
@@ -180,10 +181,11 @@
             'adm_code,clr_code,date_of_birth,email,'
             'employer,firstname,flash_notice,lastname,matric_number,middlename,'
-            'nationality,officer_comment,perm_address,personal_updated,'
+            'nationality,officer_comment,parents_email,'
+            'perm_address,personal_updated,'
             'phone,reg_number,sex,student_id,suspended,suspended_comment,'
             'password,state,history,certcode,'
             'is_postgrad,current_level,current_session\r\n'
             'my adm code,my clr code,1981-02-04#,anna@sample.com,,'
-            'Anna,,Tester,234,M.,NG,,"Studentroad 21\nLagos 123456\n"'
+            'Anna,,Tester,234,M.,NG,,,"Studentroad 21\nLagos 123456\n"'
             ',,+234-123-12345#,123,f,A111111,0,,,created'
             in result
@@ -200,10 +202,11 @@
             'adm_code,clr_code,date_of_birth,email,'
             'employer,firstname,flash_notice,lastname,matric_number,middlename,'
-            'nationality,officer_comment,perm_address,personal_updated,'
+            'nationality,officer_comment,parents_email,'
+            'perm_address,personal_updated,'
             'phone,reg_number,sex,student_id,suspended,suspended_comment,'
             'password,state,history,certcode,'
             'is_postgrad,current_level,current_session\r\n'
             'my adm code,my clr code,1981-02-04#,anna@sample.com,,'
-            'Anna,,Tester,234,M.,NG,,"Studentroad 21\nLagos 123456\n"'
+            'Anna,,Tester,234,M.,NG,,,"Studentroad 21\nLagos 123456\n"'
             ',,+234-123-12345#,123,f,A111111,0,,,created'
             in result
