Index: /main/waeup.ikoba/trunk/src/waeup/ikoba/products/export.py
===================================================================
--- /main/waeup.ikoba/trunk/src/waeup/ikoba/products/export.py	(revision 12076)
+++ /main/waeup.ikoba/trunk/src/waeup/ikoba/products/export.py	(revision 12077)
@@ -19,4 +19,5 @@
 """
 import grok
+from zope.securitypolicy.interfaces import IPrincipalRoleMap
 from waeup.ikoba.interfaces import ICSVExporter
 from waeup.ikoba.interfaces import MessageFactory as _
@@ -24,2 +25,45 @@
 from waeup.ikoba.utils.helpers import iface_names
 from waeup.ikoba.products.interfaces import IProduct
+
+
+class ProductExporter(grok.GlobalUtility, ExporterBase):
+    """Exporter for products.
+    """
+    grok.implements(ICSVExporter)
+    grok.name('products')
+
+    #: Fieldnames considered by this exporter
+    fields = tuple(sorted(iface_names(IProduct))) + (
+        'users_with_local_roles',)
+
+    #: The title under which this exporter will be displayed
+    title = _(u'Products')
+
+    def mangle_value(self, value, name, context=None):
+        """Hook for mangling values in derived classes
+        """
+        if name == 'users_with_local_roles':
+            value = []
+            role_map = IPrincipalRoleMap(context)
+            for local_role, user_name, setting in role_map.getPrincipalsAndRoles():
+                value.append({'user_name':user_name,'local_role':local_role})
+        return super(ProductExporter, self).mangle_value(
+            value, name, context)
+
+    def export(self, products, filepath=None):
+        """Export `products`, an iterable, as CSV file.
+
+        If `filepath` is ``None``, a raw string with CSV data is returned.
+        """
+        writer, outfile = self.get_csv_writer(filepath)
+        for product in products:
+            self.write_item(product, writer)
+        return self.close_outfile(filepath, outfile)
+
+    def export_all(self, site, filepath=None):
+        """Export products in productscontainer into filepath as CSV data.
+
+        If `filepath` is ``None``, a raw string with CSV data is returned.
+        """
+        products = site.get('products', {})
+        return self.export(products.values(), filepath)
Index: /main/waeup.ikoba/trunk/src/waeup/ikoba/products/tests/test_batching.py
===================================================================
--- /main/waeup.ikoba/trunk/src/waeup/ikoba/products/tests/test_batching.py	(revision 12076)
+++ /main/waeup.ikoba/trunk/src/waeup/ikoba/products/tests/test_batching.py	(revision 12077)
@@ -31,4 +31,5 @@
 from zope.catalog.interfaces import ICatalog
 from zope.interface.verify import verifyClass, verifyObject
+from zope.securitypolicy.interfaces import IPrincipalRoleManager
 
 from waeup.ikoba.app import Company
@@ -82,4 +83,6 @@
         self.app['products'][product.product_id] = self.product = product
         self.outfile = os.path.join(self.workdir, 'myoutput.csv')
+        role_manager = IPrincipalRoleManager(product)
+        role_manager.assignRoleToPrincipal(u'johnsrole', u'john')
         return
 
Index: /main/waeup.ikoba/trunk/src/waeup/ikoba/products/tests/test_browser.py
===================================================================
--- /main/waeup.ikoba/trunk/src/waeup/ikoba/products/tests/test_browser.py	(revision 12076)
+++ /main/waeup.ikoba/trunk/src/waeup/ikoba/products/tests/test_browser.py	(revision 12077)
@@ -55,5 +55,5 @@
     return None
 
-class ProductssFullSetup(FunctionalTestCase):
+class ProductsFullSetup(FunctionalTestCase):
     # A test case that only contains a setup and teardown
     #
@@ -67,5 +67,5 @@
 
     def setUp(self):
-        super(ProductssFullSetup, self).setUp()
+        super(ProductsFullSetup, self).setUp()
 
         # Setup a sample site for each test
@@ -113,9 +113,9 @@
 
     def tearDown(self):
-        super(ProductssFullSetup, self).tearDown()
+        super(ProductsFullSetup, self).tearDown()
         clearSite()
         shutil.rmtree(self.dc_root)
 
-class ProductsContainerUITests(ProductssFullSetup):
+class ProductsContainerUITests(ProductsFullSetup):
     # Tests for ProductsContainer class views and pages
 
@@ -163,5 +163,5 @@
 
 
-class ProductsUITests(ProductssFullSetup):
+class ProductsUITests(ProductsFullSetup):
     # Tests for Products class views and pages
 
Index: /main/waeup.ikoba/trunk/src/waeup/ikoba/products/tests/test_export.py
===================================================================
--- /main/waeup.ikoba/trunk/src/waeup/ikoba/products/tests/test_export.py	(revision 12077)
+++ /main/waeup.ikoba/trunk/src/waeup/ikoba/products/tests/test_export.py	(revision 12077)
@@ -0,0 +1,80 @@
+## $Id$
+##
+## Copyright (C) 2014 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
+##
+"""
+Test the product exporter.
+"""
+import os
+import grok
+import datetime
+from cStringIO import StringIO
+from zope.component import queryUtility, getUtility
+from zope.event import notify
+from zope.interface.verify import verifyObject, verifyClass
+from waeup.ikoba.interfaces import (
+    ICSVExporter, IExtFileStore, IFileStoreNameChooser)
+from waeup.ikoba.products.export import ProductExporter
+from waeup.ikoba.products.product import Product
+from waeup.ikoba.products.tests.test_batching import ProductImportExportSetup
+from waeup.ikoba.testing import FunctionalLayer
+
+
+class ProductExporterTest(ProductImportExportSetup):
+
+    layer = FunctionalLayer
+
+    def setUp(self):
+        super(ProductExporterTest, self).setUp()
+        self.setup_for_export()
+        return
+
+    def test_ifaces(self):
+        # make sure we fullfill interface contracts
+        obj = ProductExporter()
+        verifyObject(ICSVExporter, obj)
+        verifyClass(ICSVExporter,ProductExporter)
+        return
+
+    def test_get_as_utility(self):
+        # we can get an product exporter as utility
+        result = queryUtility(ICSVExporter, name="products")
+        self.assertTrue(result is not None)
+        return
+
+    def test_export(self):
+        # we can really export a product
+        exporter = ProductExporter()
+        exporter.export([self.product], self.outfile)
+        result = open(self.outfile, 'rb').read()
+        self.assertTrue(
+            'product_id,title,users_with_local_roles\r\nLIC,Unnamed,"'
+            '[{\'user_name\': u\'john\', \'local_role\': u\'johnsrole\'}]"\r\n'
+            in result
+            )
+        return
+
+    def test_export_all(self):
+        # we can really export all products
+        exporter = ProductExporter()
+        exporter.export_all(self.app, self.outfile)
+        result = open(self.outfile, 'rb').read()
+        self.assertTrue(
+            'product_id,title,users_with_local_roles\r\nLIC,Unnamed,"'
+            '[{\'user_name\': u\'john\', \'local_role\': u\'johnsrole\'}]"\r\n'
+            in result
+            )
+        return
