Index: main/waeup.kofa/trunk/CHANGES.txt
===================================================================
--- main/waeup.kofa/trunk/CHANGES.txt	(revision 14169)
+++ main/waeup.kofa/trunk/CHANGES.txt	(revision 14170)
@@ -4,4 +4,6 @@
 1.4.2.dev0 (unreleased)
 =======================
+
+* Add export purge button.
 
 * Add report purge button.
Index: main/waeup.kofa/trunk/src/waeup/kofa/browser/pages.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/browser/pages.py	(revision 14169)
+++ main/waeup.kofa/trunk/src/waeup/kofa/browser/pages.py	(revision 14170)
@@ -205,7 +205,8 @@
     return
 
-def doll_up(view, user=None):
+def doll_up(view, user=None, purge=False):
     """Doll up export jobs for displaying in table.
     """
+    ob_class = view.__implemented__.__name__.replace('waeup.kofa.','')
     job_entries = view.context.get_running_export_jobs(user)
     job_manager = getUtility(IJobManager)
@@ -234,4 +235,10 @@
                                job.failed and time_delta and \
                                time_delta.days < 1
+        if not show_download_button and purge:
+            entry = view.context.entry_from_job_id(job_id)
+            view.context.delete_export_entry(entry)
+            view.context.logger.info(
+                '%s - purged: job_id=%s' % (ob_class, job_id))
+            continue
         new_entry = dict(
             job=job_id,
@@ -1854,5 +1861,5 @@
         return sorted(title_name_tuples)
 
-    def update(self, CREATE=None, DISCARD=None, exporter=None,
+    def update(self, CREATE=None, DISCARD=None, PURGE=None, exporter=None,
                job_id=None, CANCEL=None):
         if CANCEL is not None:
@@ -1876,5 +1883,7 @@
                 '%s - discarded: job_id=%s' % (ob_class, job_id))
             self.flash(_('Discarded export') + ' %s' % job_id)
-        self.entries = doll_up(self, user=None)
+        self.entries = doll_up(self, user=None, purge=PURGE)
+        if PURGE:
+            self.flash(_('Exports successfully purged.'))
         return
 
Index: main/waeup.kofa/trunk/src/waeup/kofa/browser/reports.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/browser/reports.py	(revision 14169)
+++ main/waeup.kofa/trunk/src/waeup/kofa/browser/reports.py	(revision 14170)
@@ -54,5 +54,5 @@
             entry = self.context.report_entry_from_job_id(job_id)
             self.context.delete_report_entry(entry)
-            self.flash('Report discarded: %s' % job_id)
+            self.flash('Report %s discarded' % job_id)
             grok.getSite().logger.info(
                 '%s - report %s discarded' % (ob_class, job_id))
Index: main/waeup.kofa/trunk/src/waeup/kofa/browser/templates/datacenterexportpage.pt
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/browser/templates/datacenterexportpage.pt	(revision 14169)
+++ main/waeup.kofa/trunk/src/waeup/kofa/browser/templates/datacenterexportpage.pt	(revision 14170)
@@ -31,5 +31,6 @@
   <div class="form-group">
   <input type="submit" name="CANCEL" class="btn btn-default"
-      tal:attributes="value view/cancel_button" />
+      tal:attributes="value view/cancel_button"
+      />
   </div>
 </form>
@@ -86,5 +87,6 @@
           <a href="" class="btn btn-primary btn-xs"
                  tal:attributes="href job/download_url"
-                 tal:condition="job/show_download_button">
+                 tal:condition="job/show_download_button"
+                 i18n:translate="">
             Download</a>
           <input type="hidden" name="job_id"
@@ -92,5 +94,6 @@
           <input type="submit" class="btn btn-default btn-xs"
                  name="DISCARD" value="Discard"
-                 tal:condition="job/show_discard_button" />
+                 tal:condition="job/show_discard_button"
+                 i18n:translate=""/>
         </form>
       </td>
@@ -98,2 +101,13 @@
   </tbody>
 </table>
+
+<form method="POST" i18n:domain="waeup.kofa">
+  <div class="form-group">
+    <input type="submit" class="btn btn-primary"
+      name="PURGE" value="Purge outdated exports"
+      data-toggle="tooltip"
+      title="All exports older than 24 hours will be discarded!"
+      i18n:translate=""
+      />
+  </div>
+</form>
Index: main/waeup.kofa/trunk/src/waeup/kofa/browser/tests/test_browser.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/browser/tests/test_browser.py	(revision 14169)
+++ main/waeup.kofa/trunk/src/waeup/kofa/browser/tests/test_browser.py	(revision 14170)
@@ -24,4 +24,5 @@
 import os
 import grok
+from datetime import datetime, timedelta
 from zc.async.testing import wait_for_result
 from zope.component import createObject, getUtility
@@ -32,5 +33,5 @@
 from waeup.kofa.testing import FunctionalLayer, FunctionalTestCase
 from waeup.kofa.app import University
-from waeup.kofa.interfaces import IJobManager, IUserAccount
+from waeup.kofa.interfaces import IJobManager, IUserAccount, IKofaUtils
 from waeup.kofa.tests.test_async import FunctionalAsyncTestCase
 from waeup.kofa.university.faculty import Faculty
@@ -435,5 +436,5 @@
         # we can discard a generated export result
         self.trigger_export()
-        self.wait_for_export_job_completed()
+        job_id = self.wait_for_export_job_completed()
         self.browser.open(self.datacenter_path + '/@@export')
         self.browser.getControl("Discard").click()
@@ -443,6 +444,31 @@
         logcontent = open(logfile).read()
         self.assertTrue(
-            'zope.mgr - browser.pages.ExportCSVPage - discarded: job_id='
-            in logcontent)
+            'zope.mgr - browser.pages.ExportCSVPage - discarded: job_id=%s'
+            % job_id in logcontent)
+        return
+
+    def test_export_purge(self):
+        self.trigger_export()
+        job_id = self.wait_for_export_job_completed()
+        self.browser.open(self.datacenter_path + '/export')
+        self.browser.getControl("Purge").click()
+        # Flash message always says successful ...
+        self.assertTrue(
+            'Exports successfully purged.' in self.browser.contents)
+        # ... but the export is still there
+        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
+
+        job_id, gen_name, user = self.app['datacenter'].get_running_export_jobs()[0]
+        job = getUtility(IJobManager).get(job_id)
+        tz = getUtility(IKofaUtils).tzinfo
+        delta = timedelta(days=5)
+        setattr(job, '_begin_after', datetime.now(tz) - delta)
+        self.browser.getControl("Purge").click()
+        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
+        logfile = os.path.join(
+            self.app['datacenter'].storage, 'logs', 'datacenter.log')
+        logcontent = open(logfile).read()
+        self.assertTrue('zope.mgr - browser.pages.ExportCSVPage - '
+                        'purged: job_id=%s' % job_id in logcontent)
         return
 
