Index: /main/waeup.kofa/trunk/CHANGES.txt
===================================================================
--- /main/waeup.kofa/trunk/CHANGES.txt	(revision 16213)
+++ /main/waeup.kofa/trunk/CHANGES.txt	(revision 16214)
@@ -4,4 +4,6 @@
 1.6.1.dev0 (unreleased)
 =======================
+
+* Implement `RefereesRemindPage`.
 
 * Remove contact email header and add footer.
Index: /main/waeup.kofa/trunk/src/waeup/kofa/applicants/browser.py
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/applicants/browser.py	(revision 16213)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/applicants/browser.py	(revision 16214)
@@ -1775,4 +1775,68 @@
     grok.require('waeup.manageApplication')
 
+class RefereesRemindPage(UtilityView, grok.View):
+    """A display view for referee reports.
+    """
+    grok.context(IApplicant)
+    grok.name('remind_referees')
+    grok.require('waeup.manageApplication')
+
+    mandate_days = 31
+
+    def remindReferees(self):
+        site = grok.getSite()
+        kofa_utils = getUtility(IKofaUtils)
+        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
+        failed = ''
+        emails_sent = 0
+        for referee in self.context.referees:
+            if not referee.email_sent:
+                continue
+            # Check if referee has already created a report
+            report_exists = False
+            for report in self.context.refereereports:
+                if report.name == referee.name:
+                    report_exists = True
+            if report_exists:
+                continue
+            # If not, create new mandate
+            mandate = RefereeReportMandate(days=self.mandate_days)
+            mandate.params['name'] = referee.name
+            mandate.params['email'] = referee.email
+            mandate.params[
+                'redirect_path'] = '/applicants/%s/%s/addrefereereport' % (
+                    self.context.__parent__.code,
+                    self.context.application_number)
+            mandate.params['redirect_path2'] = ''
+            mandate.params['applicant_id'] = self.context.applicant_id
+            site['mandates'].addMandate(mandate)
+            # Send invitation email
+            args = {'mandate_id':mandate.mandate_id}
+            mandate_url = self.url(site) + '/mandate?%s' % urlencode(args)
+            url_info = u'Report link: %s' % mandate_url
+            success = kofa_utils.inviteReferee(referee, self.context, url_info)
+            if success:
+                emails_sent += 1
+                self.context.writeLogMessage(
+                    self, 'email sent: %s' % referee.email)
+                referee.email_sent = True
+            else:
+                failed += '%s ' % referee.email
+        return failed, emails_sent
+
+    def update(self):
+        if self.context.state != 'submitted':
+            self.flash(
+                _('Not allowed!'), type='danger')
+            return self.redirect(self.url(self.context))
+        failed, emails_sent = self.remindReferees()
+        msg = _('${a} referee(s) have been reminded by email.',
+                mapping = {'a':  emails_sent})
+        self.flash(msg)
+        return self.redirect(self.url(self.context))
+
+    def render(self):
+        return
+
 class RefereeReportDisplayFormPage(KofaDisplayFormPage):
     """A display view for referee reports.
@@ -1846,5 +1910,6 @@
             args = {'mandate_id':mandate.mandate_id}
             # Check if report exists.
-            # If so, redirect to the pdf file.
+            # (1) If mandate has been used to create a report, 
+            # redirect to the pdf file.
             if mandate.params.get('redirect_path2'):
                 self.redirect(
@@ -1853,4 +1918,12 @@
                     '?%s' % urlencode(args))
                 return
+            # (2) Report exists but was created with another mandate.
+            for report in self.context.refereereports:
+                if report.name == mandate.params.get('name'):
+                    self.flash(_('You have already created a '
+                                 'report with another mandate.'),
+                               type='warning')
+                    self.redirect(self.application_url())
+                    return
             # Prefill form with mandate params
             self.form_fields.get(
@@ -1885,6 +1958,10 @@
         notify(grok.ObjectModifiedEvent(self.mandates[self.mandate_id]))
         args = {'mandate_id':self.mandate_id}
-        self.redirect(self.url(report, 'referee_report.pdf')
-                      + '?%s' % urlencode(args))
+        self.flash(_('Your report has been successfully submitted. '
+                     'Please use the report link in the email again to download '
+                     'a pdf slip of your report.'))
+        #self.redirect(self.url(report, 'referee_report.pdf')
+        #              + '?%s' % urlencode(args))
+        self.redirect(self.application_url())
         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 16213)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/applicants/tests/test_browser.py	(revision 16214)
@@ -1746,5 +1746,13 @@
         self.browser.getControl(name="form.email").value = 'bb@bb.bb'
         self.browser.getControl("Submit").click()
-        # Referee wil be redirected to a pdf file
+        # Referee will be redirected to the frontpage
+        self.assertEqual(self.browser.url, 'http://localhost/app')
+        self.assertTrue('Your report has been successfully submitted. '
+                        'Please use the report link in the email again '
+                        'to download a pdf slip of your report.'
+            in self.browser.contents)
+        # If they use the mandate again, they will be redirected to a pdf file
+        self.browser.open('http://localhost/app/mandate?mandate_id=%s'
+            % mandate.mandate_id)
         self.assertEqual(self.browser.headers['Status'], '200 Ok')
         self.assertEqual(self.browser.headers['Content-Type'],
@@ -1762,4 +1770,17 @@
         self.assertEqual(self.browser.headers['Content-Type'],
                          'application/pdf')
+        # Referees can't use another mandate for adding a new report
+        mandate2 = RefereeReportMandate()
+        mandate2.params['name'] = u'John Referee'
+        mandate2.params['email'] = 'aa@aa.aa'
+        mandate2.params['applicant_id'] = self.applicant.applicant_id
+        mandate2.params['redirect_path'] = '/applicants/%s/%s/addrefereereport' % (
+                container_name_1, self.applicant.application_number)
+        mandate2.params['redirect_path2'] = ''
+        self.app['mandates'].addMandate(mandate2)
+        self.browser.open('http://localhost/app/mandate?mandate_id=%s'
+            % mandate2.mandate_id)
+        self.assertTrue('You have already created a report with another mandate'
+            in self.browser.contents)
         # Managers can view the report
         self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
@@ -1778,5 +1799,5 @@
         print "Sample PDF referee_report_slip.pdf written to %s" % path
         # Mandate is not deleted ...
-        self.assertEqual(len(self.app['mandates'].keys()), 1)
+        self.assertEqual(len(self.app['mandates'].keys()), 2)
         # ... but redirect_path2 attribute has been set
         redirect_path2 = '/applicants/%s/%s/%s/referee_report.pdf' % (
@@ -1785,5 +1806,5 @@
                 report.r_id)
         self.assertEqual(
-            self.app['mandates'].values()[0].params['redirect_path2'],
+            self.app['mandates'][mandate.mandate_id].params['redirect_path2'],
             redirect_path2)
         # Managers can delete referee reports
@@ -1831,6 +1852,4 @@
         self.browser.getControl(name="confirm_passport").value = True
         self.browser.getControl("Finally Submit").click()
-        mandate_id_0 = self.app['mandates'].keys()[0]
-        mandate_id_1 = self.app['mandates'].keys()[1]
         if self.app['mandates'].values()[0].params['name'] == 'Linda Tree':
             mandate_id_0 = self.app['mandates'].keys()[0]
@@ -1920,2 +1939,101 @@
             'invitation emails were sent.' in self.browser.contents)
         return
+
+    def test_remind_referees(self):
+        self.applicant.lastname = u'Mitchell'
+        IWorkflowState(self.applicant).setState('submitted')
+        # Add two referees
+        referee1 = RefereeEntry()
+        referee2 = RefereeEntry()
+        referee1.name = u'Linda Tree'
+        referee1.email = 'linda@forest.de'
+        referee2.name = u'Otis Stone'
+        referee2.email = 'otis@stones.de'
+        referee1.email_sent = True
+        referee2.email_sent = True
+        self.applicant.referees = [referee1, referee2]
+        # Managers can remind referees
+        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
+        self.browser.open(self.manage_path)
+        self.browser.getLink("Remind referees").click()
+        self.assertTrue('2 referee(s) have been reminded by email.'
+            in self.browser.contents)
+        logfile = os.path.join(
+            self.app['datacenter'].storage, 'logs', 'applicants.log')
+        logcontent = open(logfile).read()
+        self.assertTrue(
+            'zope.mgr - applicants.browser.RefereesRemindPage - %s - '
+            'email sent: otis@stones.de' % self.applicant.applicant_id
+            in logcontent)
+        if self.app['mandates'].values()[0].params['name'] == 'Linda Tree':
+            mandate_id_0 = self.app['mandates'].keys()[0]
+            mandate_id_1 = self.app['mandates'].keys()[1]
+        else:
+            mandate_id_0 = self.app['mandates'].keys()[1]
+            mandate_id_1 = self.app['mandates'].keys()[0]
+        self.assertMatches(
+            'Sending email from no-reply@waeup.org to linda@forest.de:'
+            '\nMessage:'
+            '\nmsg: MIME-Version: 1.0\nmsg: Content-Type: text/plain; charset="us-ascii"'
+            '\nmsg: Content-Transfer-Encoding: 7bit'
+            '\nmsg: From: Administrator <no-reply@waeup.org>'
+            '\nmsg: To: Linda Tree <linda@forest.de>'
+            '\nmsg: Reply-To: Administrator <contact@waeup.org>'
+            '\nmsg: Subject: Request for referee report from Sample University'
+            '\nmsg: '
+            '\nmsg: Dear Linda Tree,'
+            '\nmsg: '
+            '\nmsg: The candidate with Id app%s_372052 and name Joan Mitchell applied to'
+            '\nmsg: the Sample University to study Unnamed Certificate for the %s/%s session.'
+            '\nmsg: The candidate has listed you as referee. You are thus required to kindly use'
+            '\nmsg: the link below to provide your referral remarks on or before'
+            '\nmsg: 2016-08-12 08:32:41.619671+00:00.'
+            '\nmsg: '
+            '\nmsg: Report link: http://localhost/app/mandate?mandate_id=%s'
+            '\nmsg: '
+            '\nmsg: Thank You'
+            '\nmsg: '
+            '\nmsg: The Secretary'
+            '\nmsg: Post Graduate School'
+            '\nmsg: Sample University'
+            '\nmsg: '
+            '\nSending email from no-reply@waeup.org to otis@stones.de:'
+            '\nMessage:'
+            '\nmsg: MIME-Version: 1.0'
+            '\nmsg: Content-Type: text/plain; charset="us-ascii"'
+            '\nmsg: Content-Transfer-Encoding: 7bit'
+            '\nmsg: From: Administrator <no-reply@waeup.org>'
+            '\nmsg: To: Otis Stone <otis@stones.de>'
+            '\nmsg: Reply-To: Administrator <contact@waeup.org>'
+            '\nmsg: Subject: Request for referee report from Sample University'
+            '\nmsg: '
+            '\nmsg: Dear Otis Stone,'
+            '\nmsg: '
+            '\nmsg: The candidate with Id app%s_<6-DIGITS> and name Joan Mitchell applied to'
+            '\nmsg: the Sample University to study Unnamed Certificate for the %s/%s session.'
+            '\nmsg: The candidate has listed you as referee. You are thus required to kindly use'
+            '\nmsg: the link below to provide your referral remarks on or before'
+            '\nmsg: <YYYY-MM-DD hh:mm:ss>.<6-DIGITS>+00:00.'
+            '\nmsg: '
+            '\nmsg: Report link: http://localhost/app/mandate?mandate_id=%s'
+            '\nmsg: '
+            '\nmsg: Thank You'
+            '\nmsg: '
+            '\nmsg: The Secretary'
+            '\nmsg: Post Graduate School'
+            '\nmsg: Sample University'
+            '\nmsg: '
+            % (session_1, session_1, session_2, mandate_id_0,
+               session_1, session_1, session_2, mandate_id_1,),
+            self.get_fake_smtp_output()
+            )
+        # If a report exists, only one email is being sent to Otis Stone.
+        report = createObject(u'waeup.ApplicantRefereeReport')
+        report.r_id = 'any_id'
+        report.name = u'Linda Tree'
+        self.applicant[report.r_id] = report
+        self.browser.open(self.manage_path)
+        self.browser.getLink("Remind referees").click()
+        self.assertTrue('1 referee(s) have been reminded by email.'
+            in self.browser.contents)
+        return
Index: /main/waeup.kofa/trunk/src/waeup/kofa/applicants/viewlets.py
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/applicants/viewlets.py	(revision 16213)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/applicants/viewlets.py	(revision 16214)
@@ -271,4 +271,27 @@
         return self.view.url(self.view.context, self.target)
 
+class RemindRefereesActionButton(ManageActionButton):
+    grok.order(5)
+    grok.context(IApplicant)
+    grok.require('waeup.manageApplication')
+    icon = 'actionicon_alarm.png'
+    text = _('Remind referees')
+    target ='remind_referees'
+
+    @property
+    def onclick(self):
+        return "return window.confirm(%s);" % _(
+            "'Those referees, who have not yet submitted their report, will be reminded by email. Are you sure?'")
+
+    @property
+    def target_url(self):
+        """Get a URL to the target...
+        """
+        if self.context.state != 'submitted':
+            return
+        if not self.context.referees:
+            return
+        return self.view.url(self.view.context, self.target)
+
 class PaymentReceiptActionButton(ManageActionButton):
     grok.order(9) # This button should always be the last one.
