Index: main/waeup.kofa/trunk/src/waeup/kofa/students/browser.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/browser.py	(revision 9842)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/browser.py	(revision 9843)
@@ -58,4 +58,6 @@
 from waeup.kofa.university.certificate import (
     VirtualCertificateExportJobContainer,)
+from waeup.kofa.university.course import (
+    VirtualCourseExportJobContainer,)
 from waeup.kofa.university.vocabularies import course_levels
 from waeup.kofa.utils.batching import VirtualExportJobContainer
@@ -2791,6 +2793,57 @@
     """
     grok.context(VirtualCertificateExportJobContainer)
+    grok.template('exportconfig_certificate')
 
     @property
     def certcode(self):
         return self.context.__parent__.code
+
+class CourseExportJobContainerJobConfig(ExportJobContainerJobConfig):
+    """Page that configures a students export job for courses.
+
+    In contrast to department or certificate student data exports the
+    coursetickets_catalog is searched here. Therefore the update
+    method from the base class is customized.
+    """
+    grok.context(VirtualCourseExportJobContainer)
+    grok.template('exportconfig_course')
+
+    def _set_exporter_values(self):
+        # We provide only the students exporter.
+        exporters = []
+        for name in ('students',):
+            util = getUtility(ICSVExporter, name=name)
+            exporters.append((util.title, name),)
+        self.exporters = exporters
+
+    def update(self, START=None, session=None, level=None, mode=None,
+               exporter=None):
+        self._set_session_values()
+        self._set_level_values()
+        self._set_mode_values()
+        self._set_exporter_values()
+        if START is None:
+            return
+        if session == 'all':
+            session=None
+        if level == 'all':
+            level = None
+        job_id = self.context.start_export_job(exporter,
+                                      self.request.principal.id,
+                                      # Use a different catalog and
+                                      # pass different keywords than
+                                      # for the (default) students_catalog
+                                      cat='coursetickets',
+                                      session=session,
+                                      level=level,
+                                      code=self.context.__parent__.code)
+        ob_class = self.__implemented__.__name__.replace('waeup.kofa.','')
+        self.context.logger.info(
+            '%s - exported: %s (%s, %s, %s), job_id=%s'
+            % (ob_class, exporter, session, level,
+            self.context.__parent__.code, job_id))
+        self.flash(_('Export started for course tickets with') +
+                   ' level_session=%s, level=%s' % (
+                   session, level))
+        self.redirect(self.url(self.redirect_target))
+        return
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/browser_templates/exportconfig_certificate.pt
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/browser_templates/exportconfig_certificate.pt	(revision 9843)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/browser_templates/exportconfig_certificate.pt	(revision 9843)
@@ -0,0 +1,53 @@
+<p class="alert-message warning" i18n:translate="">
+  <strong>NOTICE:</strong>
+  Student data exports are very computation intensive.
+  Depending on the filter selected and the portal traffic load, exports may take
+  longer than 15 minutes. You are advised to export data outside high-traffic
+  time periods.
+</p>
+
+<br />
+
+<form i18n:domain="waeup.kofa" method="POST">
+  <label for="mode" i18n:translate="">
+    Exporter (Data Type):
+  </label>
+  <div class="input">
+    <select name="exporter">
+      <option tal:repeat="item view/exporters"
+              tal:attributes="value python:item[1]">
+        <span tal:replace="python: item[0]">Students</span>
+      </option>
+    </select>
+  </div>
+  <br />
+  <label for="session" i18n:translate="">
+    Current Session:
+  </label>
+  <div class="input">
+    <select name="session">
+      <option tal:repeat="item view/sessions"
+              tal:attributes="value python:item[1]">
+        <span tal:replace="python: item[0]">2012/2013</span>
+      </option>
+    </select>
+  </div>
+  <br />
+  <label for="level" i18n:translate="">
+    Current Level:
+  </label>
+  <div class="input">
+    <select name="level">
+      <option tal:repeat="item view/levels"
+              tal:attributes="value python:item[1]">
+        <span tal:replace="python: item[0]">200</span>
+      </option>
+    </select>
+  </div>
+  <br /><br />
+  <div class="input">
+  <input type="submit" name="START"  i18n:translate=""
+    value="Create CSV file"
+    class="btn primary" />
+  </div>
+</form>
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/browser_templates/exportconfig_course.pt
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/browser_templates/exportconfig_course.pt	(revision 9843)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/browser_templates/exportconfig_course.pt	(revision 9843)
@@ -0,0 +1,43 @@
+<form i18n:domain="waeup.kofa" method="POST">
+  <label for="mode" i18n:translate="">
+    Exporter (Data Type):
+  </label>
+  <div class="input">
+    <select name="exporter">
+      <option tal:repeat="item view/exporters"
+              tal:attributes="value python:item[1]">
+        <span tal:replace="python: item[0]">Students</span>
+      </option>
+    </select>
+  </div>
+  <br />
+  <label for="session" i18n:translate="">
+    Level Session:
+  </label>
+  <div class="input">
+    <select name="session">
+      <option tal:repeat="item view/sessions"
+              tal:attributes="value python:item[1]">
+        <span tal:replace="python: item[0]">2012/2013</span>
+      </option>
+    </select>
+  </div>
+  <br />
+  <label for="level" i18n:translate="">
+    Level:
+  </label>
+  <div class="input">
+    <select name="level">
+      <option tal:repeat="item view/levels"
+              tal:attributes="value python:item[1]">
+        <span tal:replace="python: item[0]">200</span>
+      </option>
+    </select>
+  </div>
+  <br /><br />
+  <div class="input">
+  <input type="submit" name="START"  i18n:translate=""
+    value="Create CSV file"
+    class="btn primary" />
+  </div>
+</form>
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/catalog.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/catalog.py	(revision 9842)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/catalog.py	(revision 9843)
@@ -140,2 +140,8 @@
     cat_name = 'students_catalog'
     defaults = dict(student_id=None) # make sure we get all studs by default
+
+class CourseTicketsQuery(FilteredCatalogQueryBase):
+    """Query students in a site. See waeup.kofa.catalog for more info.
+    """
+    cat_name = 'coursetickets_catalog'
+    defaults = dict(code=None) # make sure we get all tickets by default
Index: main/waeup.kofa/trunk/src/waeup/kofa/students/export.py
===================================================================
--- main/waeup.kofa/trunk/src/waeup/kofa/students/export.py	(revision 9842)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/export.py	(revision 9843)
@@ -21,5 +21,5 @@
 from datetime import datetime
 from waeup.kofa.interfaces import MessageFactory as _
-from waeup.kofa.students.catalog import StudentsQuery
+from waeup.kofa.students.catalog import StudentsQuery, CourseTicketsQuery
 from waeup.kofa.students.interfaces import (
     IStudent, IStudentStudyCourse, IStudentStudyLevel, ICourseTicket,
@@ -96,8 +96,10 @@
 
     def get_filtered(self, site, **kw):
-        """Get students filtered by keywords.
-
-        The keys must be valid students catalog index names. Returns a
-        simple empty list or a catalog result set with `Student`
+        """Get students from a catalog filtered by keywords.
+
+        students_catalog is the default catalog. The keys must be valid
+        atalog index names.
+        Returns a simple empty list, a list with `Student`
+        objects or a catalog result set with `Student`
         objects.
 
@@ -105,9 +107,16 @@
 
         """
-        # Pass only given keywords to StudentsQuery. This way we avoid
+        # Pass only given keywords to create FilteredCatalogQuery objects.
+        # This way we avoid
         # trouble with `None` value ambivalences and queries are also
         # faster (normally less indexes to ask). Drawback is, that
         # developers must look into catalog to see what keywords are
         # valid.
+        if kw.get('cat', None) == 'coursetickets':
+            coursetickets = CourseTicketsQuery(**kw).query()
+            students = []
+            for ticket in coursetickets:
+                students.append(ticket.student)
+            return list(set(students))
         query = StudentsQuery(**kw)
         return query.query()
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 9842)
+++ main/waeup.kofa/trunk/src/waeup/kofa/students/tests/test_browser.py	(revision 9843)
@@ -2798,5 +2798,4 @@
         self.browser.getControl(name="session").value = ['2004']
         self.browser.getControl(name="level").value = ['100']
-        self.browser.getControl(name="mode").value = ['ug_ft']
         self.browser.getControl("Create CSV file").click()
 
@@ -2823,5 +2822,5 @@
         self.assertTrue(
             'zope.mgr - students.browser.CertificateExportJobContainerJobConfig '
-            '- exported: students (2004, 100, ug_ft, None, CERT1), job_id=%s'
+            '- exported: students (2004, 100, None, None, CERT1), job_id=%s'
             % job_id in logcontent
             )
@@ -2835,2 +2834,48 @@
             '- discarded: job_id=%s' % job_id in logcontent
             )
+
+    def test_course_export(self):
+        self.browser.addHeader('Authorization', 'Basic mgr:mgrpw')
+        course1_path = 'http://localhost/app/faculties/fac1/dep1/courses/COURSE1'
+        self.browser.open(course1_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("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(course1_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.assertEqual(len(self.app['datacenter'].running_exports), 1)
+        job_id = self.app['datacenter'].running_exports[0][0]
+        # ... and discarded
+        self.browser.open(course1_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.CourseExportJobContainerJobConfig '
+            '- exported: students (2004, 100, COURSE1), 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
+            )
+        self.assertTrue(
+            'zope.mgr - students.browser.ExportJobContainerOverview '
+            '- discarded: job_id=%s' % job_id in logcontent
+            )
