Index: main/waeup.sirp/trunk/src/waeup/sirp/utils/tests/test_batching.py
===================================================================
--- main/waeup.sirp/trunk/src/waeup/sirp/utils/tests/test_batching.py	(revision 6273)
+++ main/waeup.sirp/trunk/src/waeup/sirp/utils/tests/test_batching.py	(revision 6273)
@@ -0,0 +1,241 @@
+##
+## test_batching.py
+## Login : <uli@pu.smp.net>
+## Started on  Fri Jun  3 00:28:43 2011 Uli Fouquet
+## $Id$
+## 
+## Copyright (C) 2011 Uli Fouquet
+## 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 doctest
+import logging
+import os
+import shutil
+import tempfile
+import unittest
+from zope import schema
+from zope.app.testing.functional import FunctionalTestCase
+from zope.component import provideUtility
+from zope.component.factory import Factory
+from zope.component.hooks import setSite, clearSite
+from zope.component.interfaces import IFactory
+from zope.interface import Interface, implements
+from waeup.sirp.app import University
+from waeup.sirp.testing import FunctionalLayer, doctestsuite_for_module
+from waeup.sirp.utils.helpers import attrs_to_fields
+
+optionflags = (
+    doctest.REPORT_NDIFF + doctest.ELLIPSIS + doctest.NORMALIZE_WHITESPACE)
+
+
+class ICave(Interface):
+    """A cave."""
+    id_num = schema.TextLine(
+        title = u'internal id',
+        default = u'default',
+        required = True,
+        readonly = True,
+        )
+    name = schema.TextLine(
+        title = u'Cave name',
+        default = u'Unnamed',
+        required = True)
+    dinoports = schema.Int(
+        title = u'Number of DinoPorts (tm)',
+        required = False,
+        default = 1)
+    owner = schema.TextLine(
+        title = u'Owner name',
+        required = True,
+        missing_value = 'Fred Estates Inc.')
+    taxpayer = schema.Bool(
+        title = u'Payes taxes',
+        required = True,
+        default = False)
+
+class Cave(object):
+    implements(ICave)
+    def __init__(self, name=u'Unnamed', dinoports=2,
+                 owner='Fred Estates Inc.', taxpayer=False):
+        self.name = name
+        self.dinoports = 2
+        self.owner = owner
+        self.taxpayer = taxpayer
+#Cave = attrs_to_fields(Cave)
+
+stoneville = dict
+
+from waeup.sirp.utils.batching import BatchProcessor
+class CaveProcessor(BatchProcessor):
+    util_name = 'caveprocessor'
+    #grok.name(util_name)
+    name = 'Cave Processor'
+    iface = ICave
+    location_fields = ['name']
+    factory_name = 'Lovely Cave'
+
+    def parentsExist(self, row, site):
+        return True
+
+    def getParent(self, row, site):
+        return stoneville
+
+    def entryExists(self, row, site):
+        return row['name'] in stoneville.keys()
+
+    def getEntry(self, row, site):
+        if not self.entryExists(row, site):
+            return None
+        return stoneville[row['name']]
+
+    def delEntry(self, row, site):
+        del stoneville[row['name']]
+
+    def addEntry(self, obj, row, site):
+        stoneville[row['name']] = obj
+
+    def updateEntry(self, obj, row, site):
+        # This is not strictly necessary, as the default
+        # updateEntry method does exactly the same
+        for key, value in row.items():
+            setattr(obj, key, value)
+
+class BatchProcessorTests(FunctionalTestCase):
+
+    layer = FunctionalLayer
+
+    def setupLogger(self):
+
+        self.logger = logging.getLogger('stoneville')
+        self.logger.setLevel(logging.DEBUG)
+        self.logger.propagate = False
+        self.logfile = os.path.join(self.workdir, 'stoneville.log')
+        self.handler = logging.FileHandler(self.logfile, 'w')
+        self.logger.addHandler(self.handler)
+
+    def setUp(self):
+        global stoneville
+        super(BatchProcessorTests, self).setUp()
+
+        # Setup a sample site for each test
+        app = University()
+        self.dc_root = tempfile.mkdtemp()
+        app['datacenter'].setStoragePath(self.dc_root)
+
+        # Prepopulate the ZODB...
+        self.getRootFolder()['app'] = app
+        self.app = self.getRootFolder()['app']
+
+        self.workdir = tempfile.mkdtemp()
+        factory = Factory(Cave)
+        provideUtility(factory, IFactory, 'Lovely Cave')
+
+        # Provide sample data
+        self.newcomers_csv = os.path.join(self.workdir, 'newcomers.csv')
+        open(self.newcomers_csv, 'wb').write(
+            """name,dinoports,owner,taxpayer
+Barneys Home,2,Barney,1
+Wilmas Asylum,1,Wilma,1
+Freds Dinoburgers,10,Fred,0
+Joeys Drive-in,110,Joey,0
+""")
+        self.setupLogger()
+        self.stoneville = stoneville
+        stoneville = dict()
+        self.resultpath = None
+        return
+
+    def tearDown(self):
+        super(BatchProcessorTests, self).tearDown()
+        shutil.rmtree(self.workdir)
+        shutil.rmtree(self.dc_root)
+        self.logger.removeHandler(self.handler)
+        clearSite()
+        if not isinstance(self.resultpath, list):
+            self.resultpath = [self.resultpath]
+        for path in self.resultpath:
+            if not isinstance(path, basestring):
+                continue
+            if not os.path.isdir(path):
+                path = os.path.dirname(path)
+            if os.path.exists(path):
+                shutil.rmtree(path)
+        return
+
+    def test_import(self):
+        processor = CaveProcessor()
+        result = processor.doImport(
+            self.newcomers_csv,
+            ['name', 'dinoports', 'owner', 'taxpayer'],
+            mode='create', user='Bob', logger=self.logger)
+        num_succ, num_fail, finished_path, failed_path = result
+        self.resultpath = [finished_path, failed_path]
+        assert num_succ == 4
+        assert num_fail == 0
+        assert finished_path.endswith('/newcomers.finished.csv')
+        assert failed_path is None
+
+    def test_import_stoneville(self):
+        processor = CaveProcessor()
+        result = processor.doImport(
+            self.newcomers_csv,
+            ['name', 'dinoports', 'owner', 'taxpayer'],
+            mode='create', user='Bob', logger=self.logger)
+        num_succ, num_fail, finished_path, failed_path = result
+        self.resultpath = [finished_path, failed_path]
+        assert len(self.stoneville) == 4
+        self.assertEqual(
+            sorted(self.stoneville.keys()),
+            [u'Barneys Home', u'Freds Dinoburgers',
+             u'Joeys Drive-in', u'Wilmas Asylum'])
+
+    def test_import_correct_type(self):
+        processor = CaveProcessor()
+        result = processor.doImport(
+            self.newcomers_csv,
+            ['name', 'dinoports', 'owner', 'taxpayer'],
+            mode='create', user='Bob', logger=self.logger)
+        num_succ, num_fail, finished_path, failed_path = result
+        self.resultpath = [finished_path, failed_path]
+        assert isinstance(self.stoneville['Barneys Home'].dinoports, int)
+
+
+    def test_log(self):
+        """
+           >>> print log_contents
+           --------------------
+           Bob: Batch processing finished: OK
+           Bob: Source: /.../newcomers.csv
+           Bob: Mode: create
+           Bob: User: Bob
+           Bob: Processing time: ... s (... s/item)
+           Bob: Processed: 4 lines (4 successful/ 0 failed)
+           --------------------
+
+        """
+        processor = CaveProcessor()
+        result = processor.doImport(
+            self.newcomers_csv,
+            ['name', 'dinoports', 'owner', 'taxpayer'],
+            mode='create', user='Bob', logger=self.logger)
+        num_succ, num_fail, finished_path, failed_path = result
+        self.resultpath = [finished_path, failed_path]
+        log_contents = open(self.logfile, 'rb').read()
+        doctest.run_docstring_examples(
+            self.test_log, locals(), False, 'test_log', None, optionflags)
+        return
+
+#def test_suite():
+#    return unittest.TestSuite()
Index: main/waeup.sirp/trunk/src/waeup/sirp/utils/tests/test_converters.py
===================================================================
--- main/waeup.sirp/trunk/src/waeup/sirp/utils/tests/test_converters.py	(revision 6268)
+++ main/waeup.sirp/trunk/src/waeup/sirp/utils/tests/test_converters.py	(revision 6273)
@@ -61,5 +61,6 @@
     name = schema.TextLine(
         title = u'Name',
-        default = u'Manfred'
+        default = u'Manfred',
+        readonly = True,
         )
     age = schema.Int(
@@ -151,9 +152,8 @@
         input_data = dict(name='Rudi', age='99')
         converter = IObjectConverter(IContact) # a converter to IContact
-        err, inv_err, new_contact = converter.applyRowData(
-            input_data, contact)
-        assert new_contact is contact
-        assert contact.name == 'Rudi'
-        assert contact.age == 99
+        err, inv_err, data = converter.fromStringDict(
+            input_data, contact)
+        assert data['name'] == 'Rudi'
+        assert data['age'] == 99
         return
 
@@ -164,10 +164,10 @@
         input_data2 = dict(vip='')
         converter = IObjectConverter(IContact) # a converter to IContact
-        err1, inv_err1, new_contact1 = converter.applyRowData(
+        err1, inv_err1, data1 = converter.fromStringDict(
             input_data1, contact1)
-        err2, inv_err2, new_contact2 = converter.applyRowData(
+        err2, inv_err2, data2 = converter.fromStringDict(
             input_data2, contact2)
-        assert contact1.vip is True
-        assert contact2.vip is False
+        assert data1['vip'] is True
+        assert data2['vip'] is False
 
     def test_int(self):
@@ -175,7 +175,7 @@
         input_data = dict(age='99')
         converter = IObjectConverter(IContact) # a converter to IContact
-        err, inv_err, new_contact = converter.applyRowData(
-            input_data, contact)
-        assert contact.age == 99
+        err, inv_err, data = converter.fromStringDict(
+            input_data, contact)
+        assert data['age'] == 99
         return
 
@@ -184,5 +184,5 @@
         input_data = dict(age='sweet sixteen')
         converter = IObjectConverter(IContact) # a converter to IContact
-        err, inv_err, new_contact = converter.applyRowData(
+        err, inv_err, new_contact = converter.fromStringDict(
             input_data, contact)
         self.assertEqual(err, [('age', u'Invalid integer data')])
@@ -193,8 +193,8 @@
         input_data = dict(name='Rudi')
         converter = IObjectConverter(IContact) # a converter to IContact
-        err, inv_err, new_contact = converter.applyRowData(
-            input_data, contact)
-        self.assertEqual(contact.name, u'Rudi')
-        assert isinstance(contact.name, unicode)
+        err, inv_err, data = converter.fromStringDict(
+            input_data, contact)
+        self.assertEqual(data['name'], u'Rudi')
+        assert isinstance(data['name'], unicode)
         return
 
@@ -203,5 +203,5 @@
         input_data = dict(name='Kevin', age='22')
         converter = IObjectConverter(IContact) # a converter to IContact
-        err, inv_err, new_contact = converter.applyRowData(
+        err, inv_err, new_contact = converter.fromStringDict(
             input_data, contact)
         self.assertEqual(inv_err, ['Kevins are age 16 or below.'])
@@ -211,24 +211,24 @@
         contact = Contact()
         converter = IObjectConverter(IContact) # a converter to IContact
-        err, inv_err, new_contact = converter.applyRowData(
+        err, inv_err, data = converter.fromStringDict(
             dict(birthday='1945/12/23'), contact)
-        assert contact.birthday == datetime.date(1945, 12, 23)
-        assert isinstance(contact.birthday, datetime.date)
-
-        err, inv_err, new_contact = converter.applyRowData(
+        assert data['birthday'] == datetime.date(1945, 12, 23)
+        assert isinstance(data['birthday'], datetime.date)
+
+        err, inv_err, data = converter.fromStringDict(
             dict(birthday='1945/23/12'), contact)
-        assert contact.birthday == datetime.date(1945, 12, 23)
-
-        err, inv_err, new_contact = converter.applyRowData(
-            dict(birthday='23/12/1945'), contact)
-        assert contact.birthday == datetime.date(1945, 12, 23)
-
-        err, inv_err, new_contact = converter.applyRowData(
-            dict(birthday='23.12.1945'), contact)
-        assert contact.birthday == datetime.date(1945, 12, 23)
-
-        err, inv_err, new_contact = converter.applyRowData(
-            dict(birthday='23-12-1945'), contact)
-        assert contact.birthday == datetime.date(1945, 12, 23)
+        assert data['birthday'] == datetime.date(1945, 12, 23)
+
+        #err, inv_err, data = converter.fromStringDict(
+        #    dict(birthday='23/12/1945'), contact)
+        #assert data['birthday'] == datetime.date(1945, 12, 23)
+
+        #err, inv_err, data = converter.fromStringDict(
+        #    dict(birthday='23.12.1945'), contact)
+        #assert data['birthday'] == datetime.date(1945, 12, 23)
+
+        #err, inv_err, data = converter.fromStringDict(
+        #    dict(birthday='23-12-1945'), contact)
+        #assert data['birthday'] == datetime.date(1945, 12, 23)
         return
 
@@ -236,5 +236,5 @@
         contact = Contact()
         converter = IObjectConverter(IContact) # a converter to IContact
-        err, inv_err, new_contact = converter.applyRowData(
+        err, inv_err, data = converter.fromStringDict(
             dict(birthday='not-a-date'), contact)
         self.assertEqual(err, [('birthday', u'Invalid datetime data')])
@@ -245,9 +245,9 @@
         converter = IObjectConverter(IContact) # a converter to IContact
         input_data = dict(name='Bruno', age='99', vip='on')
-        err, inv_err, new_contact = converter.applyRowData(
+        err, inv_err, data = converter.fromStringDict(
             input_data, contact, form_fields=form_fields_select)
-        self.assertEqual(contact.name, 'Bruno')
-        self.assertEqual(contact.age, 23)
-        self.assertEqual(contact.vip, True)
+        self.assertEqual(data['name'], 'Bruno')
+        assert 'age' not in data.keys()
+        assert data['vip'] is True
         return
 
@@ -257,24 +257,19 @@
         converter = IObjectConverter(IContact) # a converter to IContact
         input_data = dict(name='Bruno', age='99', vip='on')
-        err, inv_err, new_contact = converter.applyRowData(
+        err, inv_err, data = converter.fromStringDict(
             input_data, contact, form_fields=form_fields_omit)
-        self.assertEqual(contact.name, 'Manfred')
-        self.assertEqual(contact.age, 99)
-        self.assertEqual(contact.vip, False)
+        self.assertEqual(data['age'], 99)
+        assert 'name' not in data.keys()
+        assert 'vip' not in data.keys()
         return
 
     def test_factory(self):
-        # We can use factories to create a new object
-        #
-        # This way we turn a dict of strings and some string denoting
-        # the kind of object to be created (factory name) into a real object.
+        # We can use factories to convert values
         converter = IObjectConverter(IContact) # a converter to IContact
         # pass string ``contact`` instead of a real object
-        err, inv_err, contact = converter.applyRowData(
-            dict(name='Gabi'), 'contact')
-        # we get an object...
-        assert isinstance(contact, Contact)
-        # ...with the values from the dict set.
-        self.assertEqual(contact.name, 'Gabi')
+        err, inv_err, data = converter.fromStringDict(
+            dict(name='Gabi', age='23'), 'contact')
+        self.assertEqual(data['age'], 23)
+        self.assertEqual(data['name'], u'Gabi')
         return
 
@@ -282,8 +277,8 @@
         # We can handle vocabularies
         converter = IObjectConverter(IContact) # a converter to IContact
-        err, inv_err, contact = converter.applyRowData(
+        err, inv_err, data = converter.fromStringDict(
             dict(fav_color='blue'), 'contact')
-        assert contact.fav_color == u'blue'
-        assert isinstance(contact.fav_color, unicode)
+        assert data['fav_color'] == u'blue'
+        assert isinstance(data['fav_color'], unicode)
         return
 
@@ -291,8 +286,8 @@
         # We can handle vocabularies
         converter = IObjectConverter(IContact) # a converter to IContact
-        err, inv_err, contact = converter.applyRowData(
+        err, inv_err, data = converter.fromStringDict(
             dict(fav_color='magenta'), 'contact')
         self.assertEqual(err, [('fav_color', u'Invalid value')])
-        assert contact.fav_color == u'red'
+        assert 'fav_color' not in data.keys()
         return
 
@@ -300,8 +295,9 @@
         # We can handle vocabs with non-string values
         converter = IObjectConverter(IContact) # a converter to IContact
-        err, inv_err, contact = converter.applyRowData(
+        err, inv_err, data = converter.fromStringDict(
             dict(num_cars='1'), 'contact')
-        assert contact.num_cars == 1
-        return
+        assert data['num_cars'] == 1
+        return
+
 
 def test_suite():
