Index: main/waeup.kofa/trunk/CHANGES.txt
===================================================================
--- main/waeup.kofa/trunk/CHANGES.txt	(revision 14168)
+++ main/waeup.kofa/trunk/CHANGES.txt	(revision 14169)
@@ -4,4 +4,6 @@
 1.4.2.dev0 (unreleased)
 =======================
+
+* Add report purge button.
 
 * Add property attribute `total_score` in order to make provision
Index: main/waeup.kofa/trunk/src/waeup/kofa/browser/reports.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/browser/reports.py	(revision 14168)
+++ main/waeup.kofa/trunk/src/waeup/kofa/browser/reports.py	(revision 14169)
@@ -19,4 +19,5 @@
 """
 import grok
+from datetime import datetime, timedelta
 from zope.i18n import translate
 from zope.component import getUtility, queryUtility
@@ -25,5 +26,5 @@
 from waeup.kofa.interfaces import IJobManager, IKofaUtils
 from waeup.kofa.interfaces import MessageFactory as _
-from waeup.kofa.browser.layout import KofaPage
+from waeup.kofa.browser.layout import KofaPage, jsaction
 from waeup.kofa.utils.helpers import get_current_principal
 from waeup.kofa.reports import (
@@ -47,22 +48,36 @@
         return self.url(self.context, '%s/pdf' % job_id)
 
-    def update(self, job_id=None, DISCARD=None, DOWNLOAD=None):
+    def update(self, job_id=None, DISCARD=None, DOWNLOAD=None, PURGE=None):
         self.entries = []
+        ob_class = self.__implemented__.__name__.replace('waeup.kofa.', '')
         if job_id and DISCARD:
             entry = self.context.report_entry_from_job_id(job_id)
             self.context.delete_report_entry(entry)
             self.flash('Report discarded: %s' % job_id)
-            ob_class = self.__implemented__.__name__.replace(
-                'waeup.kofa.', '')
             grok.getSite().logger.info(
                 '%s - report %s discarded' % (ob_class, job_id))
         if not checkPermission('waeup.manageReports', self.context):
-            user = get_current_principal()
-            self.entries = self._generate_entries(user_id=user.id)
+            user_id = get_current_principal().id
         else:
-            self.entries = self._generate_entries(user_id=None)
+            user_id = None
+        self.entries = self._generate_entries(user_id=user_id)
         if job_id and DOWNLOAD:
             self.redirect(self._report_url(job_id))
             return
+        if PURGE:
+            counter = 0
+            for entry in self.context.get_running_report_jobs(user_id=user_id):
+                job_id, gen_name, user = entry
+                job = getUtility(IJobManager).get(job_id)
+                starttime = getattr(job, 'begin_after', None)
+                delta = timedelta(weeks=4)
+                tz = getUtility(IKofaUtils).tzinfo
+                if datetime.now(tz) - delta > starttime:
+                    self.context.delete_report_entry(entry)
+                    counter += 1
+                    grok.getSite().logger.info(
+                        '%s - report %s purged' % (ob_class, job_id))
+            self.flash('%s report(s) purged' % counter)
+            self.redirect(self.url(self.context))
         return
 
@@ -87,5 +102,4 @@
             entries.append(new_entry)
         return entries
-
 
 class ReportsContainerTraverser(grok.Traverser):
Index: main/waeup.kofa/trunk/src/waeup/kofa/browser/templates/reportscontainerpage.pt
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/browser/templates/reportscontainerpage.pt	(revision 14168)
+++ main/waeup.kofa/trunk/src/waeup/kofa/browser/templates/reportscontainerpage.pt	(revision 14169)
@@ -47,8 +47,18 @@
   </tbody>
 </table>
+
 <form method="POST" i18n:domain="waeup.kofa">
-  <a tal:attributes="href python:view.url(context, 'create')"
-     class="btn btn-primary" i18n:translate="">
-    Create new report
-  </a>
+  <div class="form-group">
+    <a tal:attributes="href python:view.url(context, 'create')"
+       class="btn btn-primary" i18n:translate="">
+      Create new report
+    </a>
+    <input type="submit" class="btn btn-default"
+      name="PURGE" value="Purge outdated reports"
+      data-toggle="tooltip" title="All reports older than 4 weeks will be discarded!"
+      onclick="return window.confirm('Are you really sure?')"/>
+  </div>
 </form>
+
+
+
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/reports/tests/test_student_statistics.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/reports/tests/test_student_statistics.py	(revision 14168)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/reports/tests/test_student_statistics.py	(revision 14169)
@@ -1,8 +1,9 @@
 # Tests for student related reports
 import os
+from datetime import datetime, timedelta
 from zc.async.testing import wait_for_result
 from zope.interface.verify import verifyClass, verifyObject
 from zope.component import getUtility
-from waeup.kofa.interfaces import IJobManager
+from waeup.kofa.interfaces import IJobManager, IKofaUtils
 from waeup.kofa.students.reports.student_statistics import (
     get_student_stats, StudentStatisticsReport, IStudentStatisticsReport)
@@ -207,2 +208,24 @@
             )
         return
+
+    def test_report_purge(self):
+        self.trigger_report_creation()
+        job_id = self.wait_for_report_job_completed()
+        self.browser.open('http://localhost/app/reports')
+        self.browser.getControl("Purge").click()
+        self.assertTrue('0 report(s) purged' in self.browser.contents)
+        job_id, gen_name, user = self.app['reports'].get_running_report_jobs(user_id=None)[0]
+        job = getUtility(IJobManager).get(job_id)
+        tz = getUtility(IKofaUtils).tzinfo
+        delta = timedelta(weeks=5)
+        setattr(job, '_begin_after', datetime.now(tz) - delta)
+        self.browser.getControl("Purge").click()
+        self.assertTrue('1 report(s) purged' in self.browser.contents)
+        logfile = os.path.join(
+            self.app['datacenter'].storage, 'logs', 'main.log')
+        logcontent = open(logfile).read()
+        self.assertTrue(
+            'INFO - zope.mgr - browser.reports.ReportsContainerPage - '
+            'report %s purged' % job_id in logcontent
+            )
+        return
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/student.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/student.py	(revision 14168)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/student.py	(revision 14169)
@@ -37,5 +37,6 @@
     IObjectHistory, IUserAccount, IFileStoreNameChooser, IFileStoreHandler,
     IKofaUtils, registration_states_vocab, IExtFileStore,
-    CREATED, ADMITTED, CLEARANCE, PAID, REGISTERED, VALIDATED, RETURNING)
+    CREATED, ADMITTED, CLEARANCE, PAID, REGISTERED, VALIDATED, RETURNING,
+    GRADUATED)
 from waeup.kofa.students.accommodation import StudentAccommodation
 from waeup.kofa.students.interfaces import (
@@ -218,5 +219,5 @@
     @property
     def before_payment(self):
-        non_fresh_states = (PAID, REGISTERED, VALIDATED, RETURNING, )
+        non_fresh_states = (PAID, REGISTERED, VALIDATED, RETURNING, GRADUATED)
         if self.is_fresh and self.state not in non_fresh_states:
             return True
