Index: /main/waeup.kofa/trunk/src/waeup/kofa/applicants/browser.py
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/applicants/browser.py	(revision 10654)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/applicants/browser.py	(revision 10655)
@@ -34,5 +34,6 @@
     IApplicantRegisterUpdate
     )
-from waeup.kofa.applicants.container import ApplicantsContainer
+from waeup.kofa.applicants.container import (
+    ApplicantsContainer, VirtualApplicantsExportJobContainer)
 from waeup.kofa.applicants.applicant import search
 from waeup.kofa.applicants.workflow import (
@@ -48,5 +49,6 @@
 from waeup.kofa.browser.layout import (
     NullValidator, jsaction, action, UtilityView, JSAction)
-from waeup.kofa.browser.pages import add_local_role, del_local_roles
+from waeup.kofa.browser.pages import (
+    add_local_role, del_local_roles, doll_up, ExportCSVView)
 from waeup.kofa.browser.resources import datepicker, tabs, datatable, warning
 from waeup.kofa.interfaces import (
@@ -273,4 +275,12 @@
     """
     grok.context(IApplicantsContainer)
+
+
+class ApplicantsExportsBreadcrumb(Breadcrumb):
+    """A breadcrumb for exports.
+    """
+    grok.context(VirtualApplicantsExportJobContainer)
+    title = _(u'Applicant Data Exports')
+    target = None
 
 class ApplicantBreadcrumb(Breadcrumb):
@@ -1162,2 +1172,61 @@
         self.applicant_id = applicant_id
         return
+
+class ExportJobContainerOverview(KofaPage):
+    """Page that lists active applicant data export jobs and provides links
+    to discard or download CSV files.
+
+    """
+    grok.context(VirtualApplicantsExportJobContainer)
+    grok.require('waeup.manageApplication')
+    grok.name('index.html')
+    grok.template('exportjobsindex')
+    label = _('Applicant Data Exports')
+    pnav = 3
+
+    def update(self, CREATE=None, DISCARD=None, job_id=None):
+        if CREATE:
+            self.redirect(self.url('@@start_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 ExportJobContainerJobStart(KofaPage):
+    """Page that starts an applicants export job.
+
+    """
+    grok.context(VirtualApplicantsExportJobContainer)
+    grok.require('waeup.manageApplication')
+    grok.name('start_export')
+
+    def update(self):
+        exporter = 'applicants'
+        container_code = self.context.__parent__.code
+        job_id = self.context.start_export_job(exporter,
+                                      self.request.principal.id,
+                                      container=container_code)
+
+        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
+        self.context.logger.info(
+            '%s - exported: %s (%s), job_id=%s'
+            % (ob_class, exporter, container_code, job_id))
+        self.flash(_('Export started.'))
+        self.redirect(self.url(self.context))
+        return
+
+    def render(self):
+        return
+
+class ExportJobContainerDownload(ExportCSVView):
+    """Page that downloads a students export csv file.
+
+    """
+    grok.context(VirtualApplicantsExportJobContainer)
+    grok.require('waeup.manageApplication')
Index: /main/waeup.kofa/trunk/src/waeup/kofa/applicants/browser_templates/exportjobsindex.pt
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/applicants/browser_templates/exportjobsindex.pt	(revision 10655)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/applicants/browser_templates/exportjobsindex.pt	(revision 10655)
@@ -0,0 +1,62 @@
+<table i18n:domain="waeup.kofa">
+  <thead>
+    <tr>
+      <th i18n:translate="">Export Number</th>
+      <th i18n:translate="">Exporter</th>
+      <th i18n:translate="">Filter</th>
+      <th i18n:translate="">Creator</th>
+      <th i18n:translate="">Creation Date</th>
+      <th i18n:translate="">Status</th>
+      <th>&nbsp;</th>
+    </tr>
+  </thead>
+  <tbody>
+    <tr tal:repeat="job view/entries">
+      <td>
+        <span tal:replace="job/job">12</span>
+      </td>
+      <td>
+        <span tal:replace="structure job/exporter">EXPORTER</span>
+      </td>
+      <td>
+        <span tal:replace="structure job/args">ARGS</span>
+      </td>
+      <td>
+        <span tal:replace="job/creator">CREATOR</span>
+      </td>
+      <td nowrap>
+        <span tal:replace="job/start_time">DATETIME</span>
+      </td>
+      <td>
+        <span tal:replace="job/status">STATUS</span>
+      </td>
+      <td nowrap>
+        <a href="" class="btn" i18n:translate=""
+           tal:condition="job/show_refresh_button">
+          <img tal:attributes="src static/actionicon_reload.png" />
+          Reload
+        </a>
+        <form method="POST">
+          <a href="" class="btn primary small"
+                 tal:attributes="href job/download_url"
+                 tal:condition="job/show_download_button">
+            Download</a>
+
+          <input type="hidden" name="job_id"
+                 tal:attributes="value job/job" />
+          <input type="submit" class="btn small"
+                 name="DISCARD" value="Discard"
+                 tal:condition="job/show_discard_button" />
+        </form>
+      </td>
+    </tr>
+  </tbody>
+</table>
+<form method="POST" i18n:domain="waeup.kofa">
+  <div class="span12">
+
+  </div>
+  <input class="btn primary" type="submit" name="CREATE"
+         value="Start new export"
+          />
+</form>
Index: /main/waeup.kofa/trunk/src/waeup/kofa/applicants/container.py
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/applicants/container.py	(revision 10654)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/applicants/container.py	(revision 10655)
@@ -23,4 +23,5 @@
 import pytz
 from datetime import datetime
+import zope.location.location
 from zope.component import getUtility
 from zope.component.factory import Factory
@@ -31,5 +32,5 @@
     IApplicantsUtils)
 from waeup.kofa.utils.helpers import attrs_to_fields
-
+from waeup.kofa.utils.batching import VirtualExportJobContainer
 
 def generate_applicant_id(container=None):
@@ -42,4 +43,8 @@
         # In some tests we don't use containers
         return u"xxx_1234"
+
+class VirtualApplicantsExportJobContainer(VirtualExportJobContainer):
+    """A virtual export job container for certificates.
+    """
 
 class ApplicantsContainer(grok.Container):
@@ -119,4 +124,14 @@
         return
 
+    def traverse(self, name):
+        """Deliver appropriate containers.
+        """
+        if name == 'exports':
+            # create a virtual exports container and return it
+            container = VirtualApplicantsExportJobContainer()
+            zope.location.location.located(container, self, 'exports')
+            return container
+        return None
+
 ApplicantsContainer = attrs_to_fields(ApplicantsContainer)
 
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 10654)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/applicants/tests/test_browser.py	(revision 10655)
@@ -28,4 +28,5 @@
 from datetime import datetime, date, timedelta
 from mechanize import LinkNotFoundError
+from zc.async.testing import wait_for_result
 from zope.event import notify
 from zope.catalog.interfaces import ICatalog
@@ -42,7 +43,8 @@
 from waeup.kofa.applicants.applicant import Applicant
 from waeup.kofa.interfaces import (
-    IExtFileStore, IFileStoreNameChooser, IUserAccount)
+    IExtFileStore, IFileStoreNameChooser, IUserAccount, IJobManager)
 from waeup.kofa.university.faculty import Faculty
 from waeup.kofa.university.department import Department
+from waeup.kofa.tests.test_async import FunctionalAsyncTestCase
 
 PH_LEN = 15911  # Length of placeholder file
@@ -1120,2 +1122,57 @@
             'An email with your user name and password has been sent'
             in self.browser.contents)
+
+class ApplicantsExportTests(ApplicantsFullSetup, FunctionalAsyncTestCase):
+    # Tests for StudentsContainer class views and pages
+
+    layer = FunctionalLayer
+
+    def wait_for_export_job_completed(self):
+        # helper function waiting until the current export job is completed
+        manager = getUtility(IJobManager)
+        job_id = self.app['datacenter'].running_exports[0][0]
+        job = manager.get(job_id)
+        wait_for_result(job)
+        return job_id
+
+    def test_applicants_in_container_export(self):
+        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
+        container_path = 'http://localhost/app/applicants/app2011'
+        self.browser.open(container_path)
+        self.browser.getLink("Export applicants").click()
+        self.browser.getControl("Start new export").click()
+
+        # When the job is finished and we reload the page...
+        job_id = self.wait_for_export_job_completed()
+        self.browser.open(container_path + '/exports')
+        # ... the csv file can be downloaded ...
+        self.browser.getLink("Download").click()
+        self.assertEqual(self.browser.headers['content-type'],
+            'text/csv; charset=UTF-8')
+        self.assertTrue(
+            'filename="WAeUP.Kofa_applicants_%s.csv' % job_id in
+            self.browser.headers['content-disposition'])
+        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
+        job_id = self.app['datacenter'].running_exports[0][0]
+        # ... and discarded
+        self.browser.open(container_path + '/exports')
+        self.browser.getControl("Discard").click()
+        self.assertEqual(len(self.app['datacenter'].running_exports), 0)
+        # Creation, downloading and discarding is logged
+        logfile = os.path.join(
+            self.app['datacenter'].storage, 'logs', 'datacenter.log')
+        logcontent = open(logfile).read()
+        self.assertTrue(
+            'zope.mgr - applicants.browser.ExportJobContainerJobStart - '
+            'exported: applicants (app2011), job_id=%s'
+            % job_id in logcontent
+            )
+        self.assertTrue(
+            'zope.mgr - applicants.browser.ExportJobContainerDownload '
+            '- downloaded: WAeUP.Kofa_applicants_%s.csv, job_id=%s'
+            % (job_id, job_id) in logcontent
+            )
+        self.assertTrue(
+            'zope.mgr - applicants.browser.ExportJobContainerOverview '
+            '- discarded: job_id=%s' % job_id in logcontent
+            )
Index: /main/waeup.kofa/trunk/src/waeup/kofa/applicants/viewlets.py
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/applicants/viewlets.py	(revision 10654)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/applicants/viewlets.py	(revision 10655)
@@ -138,4 +138,15 @@
     target = 'manage'
 
+class ExportApplicantsActionButton(ManageActionButton):
+    """ 'Export applicants' button for faculties.
+    """
+    grok.context(IApplicantsContainer)
+    grok.view(ApplicantsContainerPage)
+    grok.require('waeup.manageApplication')
+    icon = 'actionicon_down.png'
+    text = _('Export applicants')
+    target = 'exports'
+    grok.order(4)
+
 class ApplicantsRootCreateStudentsActionButton(ManageActionButton):
     grok.order(3)
Index: /main/waeup.kofa/trunk/src/waeup/kofa/browser/breadcrumbs.py
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/browser/breadcrumbs.py	(revision 10654)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/browser/breadcrumbs.py	(revision 10655)
@@ -169,5 +169,5 @@
 
 class ExportsBreadcrumb(Breadcrumb):
-    """A breadcrumb for reports.
+    """A breadcrumb for exports.
     """
     grok.context(IExportJobContainer)
