Index: main/kofacustom.dspg/trunk/src/kofacustom/dspg/applicants/browser.py
===================================================================
--- main/kofacustom.dspg/trunk/src/kofacustom/dspg/applicants/browser.py	(revision 15497)
+++ main/kofacustom.dspg/trunk/src/kofacustom/dspg/applicants/browser.py	(revision 15498)
@@ -19,5 +19,5 @@
 """
 import grok
-from zope.component import getUtility
+from zope.component import getUtility, queryUtility
 from zope.i18n import translate
 from hurry.workflow.interfaces import IWorkflowState
@@ -25,9 +25,15 @@
 from zope.formlib.textwidgets import BytesDisplayWidget
 from waeup.kofa.interfaces import IExtFileStore, IKofaUtils
+from waeup.kofa.browser.pages import doll_up
 from waeup.kofa.applicants.interfaces import (
-    IApplicant, IApplicantEdit)
+    IApplicant, IApplicantEdit,
+    IApplicantsContainer,)
 from waeup.kofa.applicants.browser import (ApplicantDisplayFormPage,
     ApplicantManageFormPage, ApplicantEditFormPage,
-    ApplicantsContainerPage, ApplicationFeePaymentAddPage)
+    ApplicantsContainerPage, ApplicationFeePaymentAddPage,
+    ExportJobContainerOverview,
+    ExportJobContainerJobStart,
+    ExportJobContainerDownload)
+from waeup.kofa.browser.viewlets import ManageActionButton
 from waeup.kofa.applicants.viewlets import (
     PaymentReceiptActionButton, PDFActionButton)
@@ -63,4 +69,78 @@
     ]
 
+##### Bursary exports
+
+class BursaryExportPaymentsActionButton(ManageActionButton):
+    """ 'Export payment data' button for faculties.
+    """
+    grok.context(IApplicantsContainer)
+    grok.view(ApplicantsContainerPage)
+    grok.require('waeup.exportBursaryData')
+    icon = 'actionicon_down.png'
+    text = _('Export payment data')
+    target = 'bursary_exports/index2.html'
+    grok.order(4)
+
+class BursaryExportJobContainerOverview(ExportJobContainerOverview):
+    """Page that lists active applicant data export jobs and provides links
+    to discard or download CSV files.
+    """
+    grok.require('waeup.exportBursaryData')
+    grok.name('index2.html')
+
+    def update(self, CREATE=None, DISCARD=None, job_id=None):
+        if CREATE:
+            self.redirect(self.url('@@start_bursary_export'))
+            return
+        if DISCARD and job_id:
+            entry = self.context.entry_from_job_id(job_id)
+            self.context.delete_export_entry(entry)
+            ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
+            self.context.logger.info(
+                '%s - discarded: job_id=%s' % (ob_class, job_id))
+            self.flash(_('Discarded export') + ' %s' % job_id)
+        self.entries = doll_up(self, user=self.request.principal.id)
+        return
+
+class  BursaryExportJobContainerJobStart(ExportJobContainerJobStart):
+    """View that starts export job.
+    """
+    grok.require('waeup.exportBursaryData')
+    grok.name('start_bursary_export')
+
+    def update(self):
+        utils = queryUtility(IKofaUtils)
+        if not utils.expensive_actions_allowed():
+            self.flash(_(
+                "Currently, exporters cannot be started due to high "
+                "system load. Please try again later."), type='danger')
+            self.entries = doll_up(self, user=None)
+            return
+
+        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
+        container_code = self.context.__parent__.code
+        # Start payments exporter
+        exporter = 'applicantpayments'
+        job_id = self.context.start_export_job(exporter,
+                                      self.request.principal.id,
+                                      container=container_code)
+        self.context.logger.info(
+            '%s - exported: %s (%s), job_id=%s'
+            % (ob_class, exporter, container_code, job_id))
+
+        self.flash(_('Exports started.'))
+        self.redirect(self.url(self.context))
+        return
+
+    def render(self):
+        return
+
+class BursaryExportJobContainerDownload(ExportJobContainerDownload):
+    """Page that downloads a students export csv file.
+
+    """
+    grok.require('waeup.exportBursaryData')
+    grok.context(IApplicantsContainer)
+
 class CustomApplicantsContainerPage(ApplicantsContainerPage):
     """The standard view for regular applicant containers.
