Index: /main/waeup.kofa/trunk/CHANGES.txt
===================================================================
--- /main/waeup.kofa/trunk/CHANGES.txt	(revision 13253)
+++ /main/waeup.kofa/trunk/CHANGES.txt	(revision 13254)
@@ -4,4 +4,6 @@
 1.3.2.dev0 (unreleased)
 =======================
+
+* Add public page to check application status without password.
 
 * Fix department breakdown of student statistics.
Index: /main/waeup.kofa/trunk/src/waeup/kofa/applicants/browser.py
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/applicants/browser.py	(revision 13253)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/applicants/browser.py	(revision 13254)
@@ -39,5 +39,5 @@
 from waeup.kofa.applicants.applicant import search
 from waeup.kofa.applicants.workflow import (
-    INITIALIZED, STARTED, PAID, SUBMITTED, ADMITTED)
+    INITIALIZED, STARTED, PAID, SUBMITTED, ADMITTED, NOT_ADMITTED, CREATED)
 from waeup.kofa.browser import (
 #    KofaPage, KofaEditFormPage, KofaAddFormPage, KofaDisplayFormPage,
@@ -1300,4 +1300,78 @@
         return
 
+class ApplicantCheckStatusPage(KofaPage):
+    """Captcha'd status checking page for applicants.
+    """
+    grok.context(IApplicantsRoot)
+    grok.name('checkstatus')
+    grok.require('waeup.Anonymous')
+    grok.template('applicantcheckstatus')
+    buttonname = _('Submit')
+
+    def label(self):
+        if self.result:
+            return _('Application status of ${a}',
+                     mapping = {'a':self.applicant.applicant_id})
+        return _('Check your application status')
+
+    def update(self, SUBMIT=None):
+        form = self.request.form
+        self.result = False
+        # 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)
+        if SUBMIT:
+            if not self.captcha_result.is_valid:
+                # Captcha will display error messages automatically.
+                # No need to flash something.
+                return
+            applicant_id = form.get('applicant_id', None)
+            lastname = form.get('lastname', None)
+            if not applicant_id or not lastname:
+                self.flash(
+                    _('Required input missing.'), type='warning')
+                return
+            cat = getUtility(ICatalog, name='applicants_catalog')
+            results = list(
+                cat.searchResults(applicant_id=(applicant_id, applicant_id)))
+            if results:
+                applicant = results[0]
+                if applicant.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 application record found.'), type='warning')
+                    return
+            else:
+                self.flash(_('No application record found.'), type='warning')
+                return
+            self.applicant = applicant
+            self.entry_session = "%s/%s" % (
+                applicant.__parent__.year,
+                applicant.__parent__.year+1)
+            course_admitted = getattr(applicant, 'course_admitted', None)
+            self.course_admitted = False
+            if course_admitted is not None:
+                self.course_admitted = True
+                self.longtitle = course_admitted.longtitle
+                self.department = course_admitted.__parent__.__parent__.longtitle
+                self.faculty = course_admitted.__parent__.__parent__.__parent__.longtitle
+            self.result = True
+            self.admitted = False
+            self.not_admitted = False
+            self.submitted = False
+            self.not_submitted = False
+            if applicant.state in (ADMITTED, CREATED):
+                self.admitted = True
+            if applicant.state in (NOT_ADMITTED,):
+                self.not_admitted = True
+            if applicant.state in (SUBMITTED,):
+                self.submitted = True
+            if applicant.state in (INITIALIZED, STARTED, PAID):
+                self.not_submitted = True
+        return
+
 class ExportJobContainerOverview(KofaPage):
     """Page that lists active applicant data export jobs and provides links
Index: /main/waeup.kofa/trunk/src/waeup/kofa/applicants/browser_templates/applicantcheckstatus.pt
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/applicants/browser_templates/applicantcheckstatus.pt	(revision 13254)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/applicants/browser_templates/applicantcheckstatus.pt	(revision 13254)
@@ -0,0 +1,86 @@
+<form method="post" enctype="multipart/form-data" tal:condition="not:view/result">
+<br />
+  <table class="form-table">
+    <tr>
+      <td i18n:translate="" class="fieldname">
+        Applicant Id:
+      </td>
+      <td>
+        <input class="textType half" type="text" name="applicant_id" />
+      </td>
+    </tr>
+    <tr>
+      <td i18n:translate="" class="fieldname">
+        Lastname (Surname):
+      </td>
+      <td>
+        <input class="textType half" type="text" name="lastname" />
+      </td>
+    </tr>
+    <tr tal:condition="view/captcha_code">
+      <td i18n:translate="">
+        Please solve the captcha<br />to prevent misuse of this service:
+      </td>
+      <td>
+        <div tal:content="structure view/captcha_code"></div>
+      </td>
+    </tr>
+  </table>
+  <br />
+  <input class="btn btn-primary" type="submit" name="SUBMIT"
+      tal:attributes="value view/buttonname" />
+</form>
+
+<div tal:condition="view/result">
+
+  <br />
+
+  <tal:admitted condition="view/admitted">
+    <p i18n:translate="">
+      <strong>Congratulations!</strong> You have been offered provisional
+      admission into the <span tal:replace="view/entry_session"></span>
+      Academic Session of <span tal:replace="layout/getAppTitle"></span>.
+    </p>
+    <span tal:condition="view/course_admitted">
+      <table class="form-table half">
+        <tr>
+          <td i18n:translate="">Study Course:
+          </td>
+          <td><span tal:replace="view/longtitle"></span>
+          </td>
+        </tr>
+        <tr>
+          <td i18n:translate=""> Department:
+          </td>
+          <td><span tal:replace="view/department"></span>
+          </td>
+        </tr>
+        <tr>
+          <td i18n:translate="">Faculty:
+          </td>
+          <td><span tal:replace="view/faculty"></span>
+          </td>
+        </tr>
+      </table>
+    </span>
+  </tal:admitted>
+
+  <tal:notadmitted condition="view/not_admitted">
+    <p i18n:translate="">
+      Sorry, you have not been offered admission.
+    </p>
+  </tal:notadmitted>
+
+  <tal:submitted condition="view/submitted">
+    <p i18n:translate="">
+      Your submitted application is being processed.
+    </p>
+  </tal:submitted>
+
+  <tal:notsubmitted condition="view/not_submitted">
+    <p i18n:translate="">
+      You have not yet submitted your application.
+    </p>
+  </tal:notsubmitted>
+
+</div>
Index: /main/waeup.kofa/trunk/src/waeup/kofa/applicants/browser_templates/applicantregemailsent.pt
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/applicants/browser_templates/applicantregemailsent.pt	(revision 13253)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/applicants/browser_templates/applicantregemailsent.pt	(revision 13254)
@@ -23,9 +23,9 @@
       <td i18n:translate="">User Name:</td>
       <td tal:content="view/applicant_id">ID</td>
-    <tr>
+    </tr>
     <tr>
       <td i18n:translate="">Password:</td>
       <td tal:content="view/password">PASSWORD</td>
-    <tr>
+    </tr>
   </table>
   <p>
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 13253)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/applicants/tests/test_browser.py	(revision 13254)
@@ -1399,4 +1399,37 @@
             in self.browser.contents)
 
+    def test_check_status(self):
+        self.applicant.lastname = u'Lion'
+        self.browser.open('http://localhost/app/applicants/checkstatus')
+        self.browser.getControl(name="applicant_id").value = 'nonsense'
+        self.browser.getControl(name="lastname").value = 'Lion'
+        self.browser.getControl("Submit").click()
+        self.assertTrue('No application record found' in self.browser.contents)
+        self.browser.getControl(name="applicant_id").value = self.applicant.applicant_id
+        self.browser.getControl(name="lastname").value = 'nonsense'
+        self.browser.getControl("Submit").click()
+        self.assertTrue('No application record found' in self.browser.contents)
+        self.browser.getControl(name="applicant_id").value = self.applicant.applicant_id
+        self.browser.getControl(name="lastname").value = 'Lion'
+        self.browser.getControl("Submit").click()
+        self.assertTrue('Application status of' in self.browser.contents)
+        self.assertTrue('You have not yet submitted your application' in self.browser.contents)
+        IWorkflowState(self.applicant).setState('admitted')
+        self.browser.open('http://localhost/app/applicants/checkstatus')
+        self.browser.getControl(name="applicant_id").value = self.applicant.applicant_id
+        self.browser.getControl(name="lastname").value = 'Lion'
+        self.browser.getControl("Submit").click()
+        self.assertTrue('Congratulations!' in self.browser.contents)
+        self.assertFalse('Study Course' in self.browser.contents)
+        self.applicant.course_admitted = self.certificate
+        self.browser.open('http://localhost/app/applicants/checkstatus')
+        self.browser.getControl(name="applicant_id").value = self.applicant.applicant_id
+        self.browser.getControl(name="lastname").value = 'Lion'
+        self.browser.getControl("Submit").click()
+        self.assertTrue('Congratulations!' in self.browser.contents)
+        self.assertTrue('Unnamed Certificate (CERT1)' in self.browser.contents)
+        self.assertTrue('Department of Unnamed Department (dep1)' in self.browser.contents)
+        self.assertTrue('Faculty of Unnamed Faculty (NA)' in self.browser.contents)
+
 class ApplicantsExportTests(ApplicantsFullSetup, FunctionalAsyncTestCase):
     # Tests for StudentsContainer class views and pages
