Index: /main/waeup.kofa/trunk/src/waeup/kofa/accesscodes/export.py
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/accesscodes/export.py	(revision 9261)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/accesscodes/export.py	(revision 9261)
@@ -0,0 +1,83 @@
+## $Id$
+##
+## Copyright (C) 2012 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+"""Exporters for access codes and access code batches.
+"""
+import grok
+from zope.securitypolicy.interfaces import IPrincipalRoleManager
+from waeup.kofa.interfaces import ICSVExporter
+from waeup.kofa.interfaces import MessageFactory as _
+from waeup.kofa.utils.batching import ExporterBase
+from waeup.kofa.utils.helpers import iface_names
+from waeup.kofa.accesscodes.interfaces import IAccessCode, IAccessCodeBatch
+
+class AccessCodeBatchExporter(grok.GlobalUtility, ExporterBase):
+    """Exporter for accesscodebatches.
+    """
+    grok.implements(ICSVExporter)
+    grok.name('accesscodebatches')
+
+    #: Fieldnames considered by this exporter
+    fields = tuple(sorted(iface_names(IAccessCodeBatch)))
+
+    #: The title under which this exporter will be displayed
+    title = _(u'AccessCodeBatches')
+
+    def export_all(self, site, filepath=None):
+        """Export accesscode batches into filepath as CSV data.
+
+        If `filepath` is ``None``, a raw string with CSV data is returned.
+        """
+        writer, outfile = self.get_csv_writer(filepath)
+        accesscodebatches = site.get('accesscodes', {})
+        for batch in accesscodebatches.values():
+            self.write_item(batch, writer)
+        return self.close_outfile(filepath, outfile)
+
+class AccessCodeExporter(grok.GlobalUtility, ExporterBase):
+    """Exporter for courses.
+    """
+    grok.implements(ICSVExporter)
+    grok.name('accesscodes')
+
+    #: Fieldnames considered by this exporter
+    fields = tuple(sorted(iface_names(IAccessCode)))
+
+    #: The title under which this exporter will be displayed
+    title = _(u'AccessCodes')
+
+    def mangle_value(self, value, name, context=None):
+        if name == 'random_num' and value is not None:
+            # Append hash '#' to numbers to circumvent
+            # unwanted excel automatic
+            value = str('%s#' % value)
+        return super(
+            AccessCodeExporter, self).mangle_value(
+            value, name, context=context)
+
+    def export_all(self, site, filepath=None):
+        """Export accesscodes into filepath as CSV data.
+
+        If `filepath` is ``None``, a raw string with CSV data is returned.
+        """
+        writer, outfile = self.get_csv_writer(filepath)
+        accesscodebatches = site.get('accesscodes', {})
+        for batch in accesscodebatches.values():
+            for ac in batch.values():
+                self.write_item(ac, writer)
+        return self.close_outfile(filepath, outfile)
+
Index: /main/waeup.kofa/trunk/src/waeup/kofa/accesscodes/tests/test_export.py
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/accesscodes/tests/test_export.py	(revision 9261)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/accesscodes/tests/test_export.py	(revision 9261)
@@ -0,0 +1,76 @@
+## $Id$
+##
+## Copyright (C) 2012 Uli Fouquet & Henrik Bettermann
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with this program; if not, write to the Free Software
+## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+##
+import os
+import shutil
+import tempfile
+import unittest
+from zope.component import queryUtility
+from zope.interface.verify import verifyObject, verifyClass
+from zope.securitypolicy.interfaces import IPrincipalRoleManager
+from waeup.kofa.interfaces import ICSVExporter
+from waeup.kofa.testing import KofaUnitTestLayer, FunctionalLayer
+from waeup.kofa.accesscodes.accesscode import (
+    AccessCodeBatchContainer, AccessCodeBatch)
+from waeup.kofa.accesscodes.export import AccessCodeBatchExporter
+
+
+class AccessCodeBatchExporterTest(unittest.TestCase):
+
+    layer = FunctionalLayer
+
+    def setUp(self):
+        self.workdir = tempfile.mkdtemp()
+        self.outfile = os.path.join(self.workdir, 'myoutput.csv')
+        return
+
+    def tearDown(self):
+        shutil.rmtree(self.workdir)
+        return
+
+    def test_ifaces(self):
+        # make sure we fullfill interface contracts
+        obj = AccessCodeBatchExporter()
+        verifyObject(ICSVExporter, obj)
+        verifyClass(ICSVExporter, AccessCodeBatchExporter)
+        return
+
+    def test_get_as_utility(self):
+        # we can get a faculty exporter as utility
+        result = queryUtility(ICSVExporter, name="accesscodebatches")
+        self.assertTrue(result is not None)
+        return
+
+    def test_export_all(self):
+        # we can export all faculties in a site
+        container = AccessCodeBatchContainer()
+        site = {'accesscodes':container}
+        batch1 = AccessCodeBatch('now', 'manfred', 'APP', 6.6, 0)
+        batch2 = AccessCodeBatch('now', 'manfred', 'SFE', 6.6, 0)
+        container.addBatch(batch1)
+        container.addBatch(batch2)
+        exporter = AccessCodeBatchExporter()
+        exporter.export_all(site, self.outfile)
+        result = open(self.outfile, 'rb').read()
+        self.assertEqual(
+            result,
+             'cost,creation_date,creator,disabled_num,entry_num,num,'
+             'prefix,used_num\r\n6.6,now,manfred,0,0,1,APP,0\r\n6.6,'
+             'now,manfred,0,0,0,SFE,0\r\n'
+            )
+        return
+
Index: /main/waeup.kofa/trunk/src/waeup/kofa/browser/pages.py
===================================================================
--- /main/waeup.kofa/trunk/src/waeup/kofa/browser/pages.py	(revision 9260)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/browser/pages.py	(revision 9261)
@@ -33,4 +33,5 @@
     )
 from zope.event import notify
+from zope.security import checkPermission
 from zope.securitypolicy.interfaces import IPrincipalRoleManager
 from zope.session.interfaces import ISession
@@ -1386,4 +1387,7 @@
         title_name_tuples = [
             (util.title, name) for name, util in utils]
+        # The exporter for access codes requires a special permission.
+        if not checkPermission('waeup.manageACBatches', self.context):
+            title_name_tuples.remove((u'AccessCodes', u'accesscodes'))
         return sorted(title_name_tuples)
 
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 9260)
+++ /main/waeup.kofa/trunk/src/waeup/kofa/browser/tests/test_browser.py	(revision 9261)
@@ -26,4 +26,5 @@
 from zope.component.hooks import setSite, clearSite
 from zope.security.interfaces import Unauthorized
+from zope.securitypolicy.interfaces import IPrincipalRoleManager
 from zope.testbrowser.testing import Browser
 from waeup.kofa.testing import FunctionalLayer, FunctionalTestCase
@@ -213,4 +214,56 @@
         return
 
+    def test_export_accesscodes(self):
+        # Create portal manager and CCOfficer
+        self.app['users'].addUser('mrmanager', 'mrmanagersecret')
+        self.app['users']['mrmanager'].email = 'mrmanager@foo.ng'
+        self.app['users']['mrmanager'].title = 'Carlo Pitter'
+        # Assign PortalManager role
+        prmlocal = IPrincipalRoleManager(self.app)
+        prmlocal.assignRoleToPrincipal('waeup.PortalManager', 'mrmanager')
+        self.app['users'].addUser('mrofficer', 'mrofficersecret')
+        self.app['users']['mrofficer'].email = 'mrofficer@foo.ng'
+        self.app['users']['mrofficer'].title = 'Carlos Potter'
+        # Assign CCOfficer role
+        prmlocal.assignRoleToPrincipal('waeup.CCOfficer', 'mrofficer')
+
+        # Login as portal manager
+        self.browser.open('http://localhost/app/login')
+        self.browser.getControl(name="form.login").value = 'mrmanager'
+        self.browser.getControl(name="form.password").value = 'mrmanagersecret'
+        self.browser.getControl("Login").click()
+
+        self.browser.open(self.datacenter_path + '/export')
+        self.assertTrue(
+            '<option value="accesscodebatches">' in self.browser.contents)
+        self.assertTrue('<option value="accesscodes">' in self.browser.contents)
+        self.browser.getControl(name="exporter").value = ['accesscodes']
+        self.browser.getControl("Create CSV file").click()
+        job_id = self.wait_for_export_job_completed()
+        try:
+            self.browser.getControl("Reload").click()
+        except LookupError:
+            pass
+        self.browser.getControl("Download").click()
+        self.assertEqual(self.browser.headers['content-type'],
+                         'text/csv; charset=UTF-8')
+        self.assertEqual(
+            'batch_num,batch_prefix,batch_serial,cost,history,owner,'
+            'random_num,representation,state\r\n',
+            self.browser.contents)
+
+        # Login as officer who is not allowed to download accesscodes
+        self.browser.open('http://localhost/app/login')
+        self.browser.getControl(name="form.login").value = 'mrofficer'
+        self.browser.getControl(name="form.password").value = 'mrofficersecret'
+        self.browser.getControl("Login").click()
+
+        self.browser.open(self.datacenter_path + '/export')
+        # The CC Officer can see the accesscodebatches exporter ...
+        self.assertTrue(
+            '<option value="accesscodebatches">' in self.browser.contents)
+        # ... but not the accesscodes exporter.
+        self.assertFalse('<option value="accesscodes">' in self.browser.contents)
+
     def test_export_discard(self):
         # we can discard a generated export result
