Index: main/waeup.kofa/trunk/CHANGES.txt
===================================================================
--- main/waeup.kofa/trunk/CHANGES.txt	(revision 12631)
+++ main/waeup.kofa/trunk/CHANGES.txt	(revision 12632)
@@ -5,5 +5,7 @@
 =======================
 
-* StudentOnlinePaymentProcessor: Raise DuplicationError if payment 
+* Add components to  configure a student export jobs in faculties.
+
+* StudentOnlinePaymentProcessor: Raise DuplicationError if payment
   (in state paid) with same p_session and p_category exists.
 
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 12631)
+++ main/waeup.kofa/trunk/src/waeup/kofa/browser/tests/test_browser.py	(revision 12632)
@@ -347,5 +347,5 @@
         self.assertTrue(
             'zope.mgr - students.browser.DatacenterExportJobContainerJobConfig '
-            '- exported: students (None, None, None, None, None, None, None), '
+            '- exported: students (None, None, None, None, None, None, None, None), '
             'job_id=%s'
             % job_id in logcontent)
Index: main/waeup.kofa/trunk/src/waeup/kofa/browser/viewlets.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/browser/viewlets.py	(revision 12631)
+++ main/waeup.kofa/trunk/src/waeup/kofa/browser/viewlets.py	(revision 12632)
@@ -444,4 +444,16 @@
     target = 'find_students'
 
+class ExportFacultyStudentsActionButton(ManageActionButton):
+    """ 'Export student data' button for faculties.
+    """
+    grok.order(3)
+    grok.context(IFaculty)
+    grok.view(FacultyPage)
+    grok.name('exportfacultystudents')
+    grok.require('waeup.showStudents')
+    icon = 'actionicon_down.png'
+    text = _('Export student data')
+    target = 'exports'
+
 class ManageDepartmentActionButton(ManageActionButton):
     """ 'Manage settings' button for departments.
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/browser.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/browser.py	(revision 12631)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/browser.py	(revision 12632)
@@ -53,6 +53,6 @@
 from waeup.kofa.university.interfaces import (
     IDepartment, ICertificate, ICourse)
-from waeup.kofa.university.department import (
-    VirtualDepartmentExportJobContainer,)
+from waeup.kofa.university.faculty import VirtualFacultyExportJobContainer
+from waeup.kofa.university.department import VirtualDepartmentExportJobContainer
 from waeup.kofa.university.facultiescontainer import (
     VirtualFacultiesExportJobContainer, FacultiesContainer)
@@ -3192,4 +3192,8 @@
 
     @property
+    def faccode(self):
+        return None
+
+    @property
     def depcode(self):
         return None
@@ -3227,6 +3231,10 @@
         if payments_end == '':
             payments_end = None
-        if (mode, level, session,
-            self.depcode, self.certcode) == (None, None, None, None, None):
+        if (mode,
+            level,
+            session,
+            self.faccode,
+            self.depcode,
+            self.certcode) == (None, None, None, None, None, None):
             # Export all students including those without certificate
             if payments_start:
@@ -3245,4 +3253,5 @@
                                               current_level=level,
                                               current_mode=mode,
+                                              faccode=self.faccode,
                                               depcode=self.depcode,
                                               certcode=self.certcode,
@@ -3255,11 +3264,12 @@
                                               current_level=level,
                                               current_mode=mode,
+                                              faccode=self.faccode,
                                               depcode=self.depcode,
                                               certcode=self.certcode)
         ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
         self.context.logger.info(
-            '%s - exported: %s (%s, %s, %s, %s, %s, %s, %s), job_id=%s'
-            % (ob_class, exporter, session, level, mode, self.depcode,
-            self.certcode, payments_start, payments_end, job_id))
+            '%s - exported: %s (%s, %s, %s, %s, %s, %s, %s, %s), job_id=%s'
+            % (ob_class, exporter, session, level, mode, self.faccode,
+            self.depcode, self.certcode, payments_start, payments_end, job_id))
         self.flash(_('Export started for students with') +
                    ' current_session=%s, current_level=%s, study_mode=%s' % (
@@ -3317,4 +3327,15 @@
     """
     grok.context(VirtualFacultiesExportJobContainer)
+
+
+class FacultyExportJobContainerJobConfig(ExportJobContainerJobConfig):
+    """Page that configures a students export job in faculties.
+
+    """
+    grok.context(VirtualFacultyExportJobContainer)
+
+    @property
+    def faccode(self):
+        return self.context.__parent__.code
 
 class DepartmentExportJobContainerJobConfig(ExportJobContainerJobConfig):
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_browser.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_browser.py	(revision 12631)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_browser.py	(revision 12632)
@@ -3299,5 +3299,5 @@
         self.assertTrue(
             'zope.mgr - students.browser.DatacenterExportJobContainerJobConfig '
-            '- exported: bursary (2004, 100, ug_ft, None, None, '
+            '- exported: bursary (2004, 100, ug_ft, None, None, None, '
             '13/12/2012, 14/12/2012), job_id=%s'
             % job_id in logcontent
@@ -3389,5 +3389,5 @@
         self.assertTrue(
             'zope.mgr - students.browser.FacultiesExportJobContainerJobConfig '
-            '- exported: bursary (2004, 100, ug_ft, None, None, '
+            '- exported: bursary (2004, 100, ug_ft, None, None, None, '
             '13/12/2012, 14/12/2012), job_id=%s'
             % job_id in logcontent
@@ -3396,4 +3396,65 @@
             'zope.mgr - students.browser.ExportJobContainerDownload '
             '- downloaded: WAeUP.Kofa_bursary_%s.csv, job_id=%s'
+            % (job_id, job_id) in logcontent
+            )
+        self.assertTrue(
+            'zope.mgr - students.browser.ExportJobContainerOverview '
+            '- discarded: job_id=%s' % job_id in logcontent
+            )
+
+    def test_faculty_export(self):
+        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
+        fac1_path = 'http://localhost/app/faculties/fac1'
+        self.browser.open(fac1_path)
+        self.browser.getLink("Export student data").click()
+        self.browser.getControl("Configure new export").click()
+        self.browser.getControl(name="exporter").value = ['students']
+        self.browser.getControl(name="session").value = ['2004']
+        self.browser.getControl(name="level").value = ['100']
+        self.browser.getControl(name="mode").value = ['ug_ft']
+        # The testbrowser does not hide the payment period fields, but
+        # values are ignored when using the students exporter.
+        self.browser.getControl(name="payments_start").value = '13/12/2012'
+        self.browser.getControl(name="payments_end").value = '14/12/2012'
+        self.browser.getControl("Create CSV file").click()
+
+        # When the job is finished and we reload the page...
+        job_id = self.wait_for_export_job_completed()
+        self.browser.open(fac1_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_students_%s.csv' % job_id in
+            self.browser.headers['content-disposition'])
+        self.assertTrue(
+            'adm_code,clearance_locked,clr_code,date_of_birth,email,employer,'
+            'firstname,lastname,matric_number,middlename,nationality,'
+            'officer_comment,perm_address,personal_updated,phone,reg_number,'
+            'sex,student_id,suspended,suspended_comment,transcript_comment,'
+            'password,state,history,certcode,is_postgrad,current_level,'
+            'current_session\r\n'
+            ',1,,1981-02-04#,aa@aa.ng,,Anna,Tester,234,,,,,,'
+            '1234#,123,m,K1000000,0,,,{SSHA}' in self.browser.contents)
+        self.assertEqual(len(self.app['datacenter'].running_exports), 1)
+        job_id = self.app['datacenter'].running_exports[0][0]
+        # ... and discarded
+        self.browser.open(fac1_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 - students.browser.FacultyExportJobContainerJobConfig '
+            '- exported: students (2004, 100, ug_ft, fac1, None, None, '
+            '13/12/2012, 14/12/2012), job_id=%s'
+            % job_id in logcontent
+            )
+        self.assertTrue(
+            'zope.mgr - students.browser.ExportJobContainerDownload '
+            '- downloaded: WAeUP.Kofa_students_%s.csv, job_id=%s'
             % (job_id, job_id) in logcontent
             )
@@ -3441,5 +3502,5 @@
         self.assertTrue(
             'zope.mgr - students.browser.DepartmentExportJobContainerJobConfig '
-            '- exported: students (2004, 100, ug_ft, dep1, None, '
+            '- exported: students (2004, 100, ug_ft, None, dep1, None, '
             '13/12/2012, 14/12/2012), job_id=%s'
             % job_id in logcontent
@@ -3488,5 +3549,5 @@
         self.assertTrue(
             'zope.mgr - students.browser.CertificateExportJobContainerJobConfig '
-            '- exported: students (2004, 100, None, None, CERT1, None, None), '
+            '- exported: students (2004, 100, None, None, None, CERT1, None, None), '
             'job_id=%s'
             % job_id in logcontent
Index: main/waeup.kofa/trunk/src/waeup/kofa/university/faculty.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/university/faculty.py	(revision 12631)
+++ main/waeup.kofa/trunk/src/waeup/kofa/university/faculty.py	(revision 12632)
@@ -20,8 +20,10 @@
 
 import grok
+import zope.location.location
 from zope.component.interfaces import IFactory
 from zope.interface import implementedBy
 from zope.component import getUtility
 from waeup.kofa.interfaces import IKofaUtils
+from waeup.kofa.utils.batching import VirtualExportJobContainer
 from waeup.kofa.university.interfaces import (
     IFaculty, IDepartment)
@@ -34,4 +36,8 @@
         insttypes_dict[inst.title_prefix],
         inst.title, inst.code)
+
+class VirtualFacultyExportJobContainer(VirtualExportJobContainer):
+    """A virtual export job container for facultiess.
+    """
 
 class Faculty(grok.Container):
@@ -67,4 +73,14 @@
         self.code = code
 
+    def traverse(self, name):
+        """Deliver appropriate containers.
+        """
+        if name == 'exports':
+            # create a virtual exports container and return it
+            container = VirtualFacultyExportJobContainer()
+            zope.location.location.located(container, self, 'exports')
+            return container
+        return None
+
     def addDepartment(self, department):
         """Add a department to the faculty.
