Index: /main/waeup.kofa/trunk/src/waeup/kofa/students/browser.py
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/students/browser.py	(revision 8778)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/students/browser.py	(revision 8779)
@@ -39,4 +39,5 @@
 from waeup.kofa.browser.resources import datepicker, datatable, tabs, warning
 from waeup.kofa.browser.layout import jsaction, action, UtilityView
+from waeup.kofa.browser.interfaces import ICaptchaManager
 from waeup.kofa.interfaces import (
     IKofaObject, IUserAccount, IExtFileStore, IPasswordValidator, IContactForm,
@@ -53,8 +54,8 @@
     IStudentAccommodation, IStudentStudyLevel,
     ICourseTicket, ICourseTicketAdd, IStudentPaymentsContainer,
-    IStudentOnlinePayment, IBedTicket, IStudentsUtils
+    IStudentOnlinePayment, IBedTicket, IStudentsUtils, IStudentRequestPW
     )
 from waeup.kofa.students.catalog import search
-from waeup.kofa.students.workflow import (ADMITTED, PAID,
+from waeup.kofa.students.workflow import (CREATED, ADMITTED, PAID,
     CLEARANCE, REQUESTED, RETURNING, CLEARED, REGISTERED, VALIDATED)
 from waeup.kofa.students.studylevel import StudentStudyLevel, CourseTicket
@@ -63,4 +64,6 @@
 from waeup.kofa.hostels.hostel import NOT_OCCUPIED
 from waeup.kofa.utils.helpers import get_current_principal, to_timezone
+
+grok.context(IKofaObject) # Make IKofaObject the default context
 
 # Save function used for save methods in pages
@@ -1915,2 +1918,93 @@
             mapping = {'a':self.student_id}))
         return
+
+class StudentRequestPasswordPage(KofaAddFormPage):
+    """Captcha'd registration page for applicants.
+    """
+    grok.name('requestpw')
+    grok.require('waeup.Anonymous')
+    grok.template('requestpw')
+    form_fields = grok.AutoFields(IStudentRequestPW).select(
+        'firstname','reg_number','email')
+    label = _('Request password for first-time login')
+
+    def update(self):
+        # Handle captcha
+        self.captcha = getUtility(ICaptchaManager).getCaptcha()
+        self.captcha_result = self.captcha.verify(self.request)
+        self.captcha_code = self.captcha.display(self.captcha_result.error_code)
+        return
+
+    def _redirect(self, email, password, student_id):
+        # Forward only email to landing page in base package.
+        self.redirect(self.url(self.context, 'requestpw_complete',
+            data = dict(email=email)))
+        return
+
+    def _pw_used(self):
+        # XXX: False if password has been used
+        return True
+
+    @action(_('Get login credentials'), 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
+        reg_number = data.get('reg_number','')
+        firstname = data.get('firstname','')
+        cat = getUtility(ICatalog, name='students_catalog')
+        results = list(
+            cat.searchResults(reg_number=(reg_number, reg_number)))
+        if results:
+            student = results[0]
+            if getattr(student,'firstname',None) is None:
+                self.flash(_('An error occurred.'))
+                return
+            elif student.firstname.lower() != firstname.lower():
+                # Don't tell the truth here. Anonymous must not
+                # know that a record was found and only the firstname
+                # verification failed.
+                self.flash(_('No student record found.'))
+                return
+            elif student.password is not None and self._pw_used:
+                self.flash(_('Your password has already been set and used. '
+                             'Please proceed to the login page.'))
+                return
+            # Store email address but nothing else.
+            student.email = data['email']
+            notify(grok.ObjectModifiedEvent(student))
+        else:
+            # No record found, this is the truth.
+            self.flash(_('No student record found.'))
+            return
+
+        kofa_utils = getUtility(IKofaUtils)
+        password = kofa_utils.genPassword()
+        IUserAccount(student).setPassword(password)
+        # Send email with credentials
+        login_url = self.url(grok.getSite(), 'login')
+        msg = _('You have successfully requested a password for the')
+        if kofa_utils.sendCredentials(IUserAccount(student),
+            password, login_url, msg):
+            email_sent = student.email
+        else:
+            email_sent = None
+        self._redirect(email=email_sent, password=password,
+            student_id=student.student_id)
+        return
+
+class StudentRequestPasswordEmailSent(KofaPage):
+    """Landing page after successful password request.
+
+    """
+    grok.name('requestpw_complete')
+    grok.require('waeup.Public')
+    grok.template('requestpwmailsent')
+    label = _('Your password request was successful.')
+
+    def update(self, email=None, student_id=None, password=None):
+        self.email = email
+        self.password = password
+        self.student_id = student_id
+        return
Index: /main/waeup.kofa/trunk/src/waeup/kofa/students/interfaces.py
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/students/interfaces.py	(revision 8778)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/students/interfaces.py	(revision 8779)
@@ -281,4 +281,26 @@
         )
 
+class IStudentRequestPW(IStudent):
+    """Representation of an student for first-time password request.
+
+    This interface is used when students use the requestpw page to
+    login for the the first time.
+    """
+    reg_number = schema.TextLine(
+        title = u'Registration Number',
+        required = True,
+        )
+
+    firstname = schema.TextLine(
+        title = _(u'First Name'),
+        required = True,
+        )
+
+    email = schema.ASCIILine(
+        title = _(u'Email Address'),
+        required = True,
+        constraint=validate_email,
+        )
+
 class IStudentStudyCourse(IKofaObject):
     """A container for student study levels.
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 8778)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_browser.py	(revision 8779)
@@ -1844,5 +1844,5 @@
 
     def test_change_password_request(self):
-        self.browser.open('http://localhost/app/sendpw')
+        self.browser.open('http://localhost/app/changepw')
         self.browser.getControl(name="form.identifier").value = '123'
         self.browser.getControl(name="form.email").value = 'aa@aa.ng'
@@ -1887,2 +1887,50 @@
         self.browser.open(self.edit_clearance_path)
         self.assertTrue('Employer' in self.browser.contents)
+
+class StudentRequestPWTests(StudentsFullSetup):
+    # Tests for student registration
+
+    layer = FunctionalLayer
+
+    def test_request_pw(self):
+        # Student with wrong reg_number can't be found.
+        self.browser.open('http://localhost/app/requestpw')
+        self.browser.getControl(name="form.firstname").value = 'Anna'
+        self.browser.getControl(name="form.reg_number").value = 'anynumber'
+        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
+        self.browser.getControl("Get login credentials").click()
+        self.assertTrue('No student record found.'
+            in self.browser.contents)
+        # Anonymous is not informed that firstname verification failed.
+        # It seems that the record doesn't exist.
+        self.browser.open('http://localhost/app/requestpw')
+        self.browser.getControl(name="form.firstname").value = 'Johnny'
+        self.browser.getControl(name="form.reg_number").value = '123'
+        self.browser.getControl(name="form.email").value = 'xx@yy.zz'
+        self.browser.getControl("Get login credentials").click()
+        self.assertTrue('No student record found.'
+            in self.browser.contents)
+        # Even with the correct firstname we can't register if a
+        # password has been set and used.
+        self.browser.getControl(name="form.firstname").value = 'Anna'
+        self.browser.getControl(name="form.reg_number").value = '123'
+        self.browser.getControl("Get login credentials").click()
+        self.assertTrue('Your password has already been set and used.'
+            in self.browser.contents)
+        self.browser.open('http://localhost/app/requestpw')
+        self.app['students'][self.student_id].password = None
+        # The firstname field, used for verification, is not case-sensitive.
+        self.browser.getControl(name="form.firstname").value = 'aNNa'
+        self.browser.getControl(name="form.reg_number").value = '123'
+        self.browser.getControl(name="form.email").value = 'new@yy.zz'
+        self.browser.getControl("Get login credentials").click()
+        # Yeah, we succeded ...
+        self.assertTrue('Your password request was successful.'
+            in self.browser.contents)
+        # ... and  student can be found in the catalog via the email address
+        cat = queryUtility(ICatalog, name='students_catalog')
+        results = list(
+            cat.searchResults(
+            email=('new@yy.zz', 'new@yy.zz')))
+        self.assertEqual(self.student,results[0])
+        return
