Index: pykota/trunk/tests/filldb.py
===================================================================
--- pykota/trunk/tests/filldb.py (revision 2748)
+++ pykota/trunk/tests/filldb.py (revision 2773)
@@ -72,5 +72,5 @@
     usernames = [ "test-user-%05i" % i for i in range(number) ]
     argsfile = open("arguments.list", "w")
-    argsfile.write('--add\n--limitby\nbalance\n--balance\n50.0\n--description\n"an user"\n')
+    argsfile.write('--add\n--limitby\nbalance\n--balance\n50.0\n--description\n"an user"\n--comment\n"fake payment"\n')
     for uname in usernames :
         argsfile.write("%s\n" % uname)
Index: pykota/trunk/pykota/storages/mysqlstorage.py
===================================================================
--- pykota/trunk/pykota/storages/mysqlstorage.py (revision 2741)
+++ pykota/trunk/pykota/storages/mysqlstorage.py (revision 2773)
@@ -130,4 +130,5 @@
             self.cursor.execute(query)
         except self.database.Error, msg :    
+            self.tool.logdebug("Query failed : %s" % repr(msg))
             raise PyKotaStorageError, str(msg)
         else :    
Index: pykota/trunk/pykota/storages/pgstorage.py
===================================================================
--- pykota/trunk/pykota/storages/pgstorage.py (revision 2741)
+++ pykota/trunk/pykota/storages/pgstorage.py (revision 2773)
@@ -115,4 +115,5 @@
             result = self.database.query(query)
         except PGError, msg :    
+            self.tool.logdebug("Query failed : %s" % repr(msg))
             raise PyKotaStorageError, str(msg)
         else :    
Index: pykota/trunk/pykota/storages/sql.py
===================================================================
--- pykota/trunk/pykota/storages/sql.py (revision 2771)
+++ pykota/trunk/pykota/storages/sql.py (revision 2773)
@@ -562,6 +562,7 @@
         
     def addUser(self, user) :        
-        """Adds a user to the quota storage, returns it."""
-        self.doModify("INSERT INTO users (username, limitby, balance, lifetimepaid, email, overcharge, description) VALUES (%s, %s, %s, %s, %s, %s, %s)" % \
+        """Adds a user to the quota storage, returns the old value if it already exists."""
+        try :
+            self.doModify("INSERT INTO users (username, limitby, balance, lifetimepaid, email, overcharge, description) VALUES (%s, %s, %s, %s, %s, %s, %s)" % \
                                          (self.doQuote(self.userCharsetToDatabase(user.Name)), \
                                           self.doQuote(user.LimitBy or 'quota'), \
@@ -571,13 +572,30 @@
                                           self.doQuote(user.OverCharge), \
                                           self.doQuote(self.userCharsetToDatabase(user.Description))))
-        return self.getUser(user.Name)
+        except PyKotaStorageError :    
+            # TODO : check if this is an error different from a duplicate insert
+            # return the existing entry which has to be modified
+            return self.getUser(user.Name)
+        else :    
+            if user.PaymentsBacklog :
+                for (value, comment) in user.PaymentsBacklog :
+                    self.writeNewPayment(user, value, comment)
+                user.PaymentsBacklog = []
+            user.isDirty = False
+            return None # the entry created doesn't need further modification
         
     def addGroup(self, group) :        
-        """Adds a group to the quota storage, returns it."""
-        self.doModify("INSERT INTO groups (groupname, limitby, description) VALUES (%s, %s, %s)" % \
-                                          (self.doQuote(self.userCharsetToDatabase(group.Name)), \
-                                           self.doQuote(group.LimitBy or "quota"), \
-                                           self.doQuote(self.userCharsetToDatabase(group.Description))))
-        return self.getGroup(group.Name)
+        """Adds a group to the quota storage, returns the old value if it already exists."""
+        try :
+            self.doModify("INSERT INTO groups (groupname, limitby, description) VALUES (%s, %s, %s)" % \
+                                  (self.doQuote(self.userCharsetToDatabase(group.Name)), \
+                                   self.doQuote(group.LimitBy or "quota"), \
+                                   self.doQuote(self.userCharsetToDatabase(group.Description))))
+        except PyKotaStorageError :    
+            # TODO : check if this is an error different from a duplicate insert
+            # return the existing entry which has to be modified
+            return self.getGroup(group.Name)
+        else :    
+            group.isDirty = False
+            return None # the entry created doesn't need further modification
 
     def addUserToGroup(self, user, group) :    
@@ -693,5 +711,8 @@
     def writeNewPayment(self, user, amount, comment="") :
         """Adds a new payment to the payments history."""
-        self.doModify("INSERT INTO payments (userid, amount, description) VALUES (%s, %s, %s)" % (self.doQuote(user.ident), self.doQuote(amount), self.doQuote(self.userCharsetToDatabase(comment))))
+        if user.ident is not None :
+            self.doModify("INSERT INTO payments (userid, amount, description) VALUES (%s, %s, %s)" % (self.doQuote(user.ident), self.doQuote(amount), self.doQuote(self.userCharsetToDatabase(comment))))
+        else :    
+            self.doModify("INSERT INTO payments (userid, amount, description) VALUES ((SELECT id FROM users WHERE username=%s), %s, %s)" % (self.doQuote(self.userCharsetToDatabase(user.Name)), self.doQuote(amount), self.doQuote(self.userCharsetToDatabase(comment))))
         
     def writeLastJobSize(self, lastjob, jobsize, jobprice) :        
Index: pykota/trunk/pykota/storages/ldapstorage.py
===================================================================
--- pykota/trunk/pykota/storages/ldapstorage.py (revision 2768)
+++ pykota/trunk/pykota/storages/ldapstorage.py (revision 2773)
@@ -909,5 +909,8 @@
         
     def addUser(self, user) :        
-        """Adds a user to the quota storage, returns it."""
+        """Adds a user to the quota storage, returns the old value if it already exists."""
+        oldentry = self.getUser(user.Name)
+        if oldentry.Exists :
+            return oldentry # we return the existing entry
         uname = self.userCharsetToDatabase(user.Name)
         newfields = {
@@ -971,9 +974,17 @@
                 dn = "%s=%s,%s" % (self.info["balancerdn"], uname, self.info["balancebase"])
                 self.doAdd(dn, fields)
-            
-        return self.getUser(user.Name)
+        user.idbalance = dn
+        if user.PaymentsBacklog :
+            for (value, comment) in user.PaymentsBacklog :
+                self.writeNewPayment(user, value, comment)
+            user.PaymentsBacklog = []
+        user.isDirty = False
+        return None # the entry created doesn't need further modification
         
     def addGroup(self, group) :        
-        """Adds a group to the quota storage, returns it."""
+        """Adds a group to the quota storage, returns the old value if it already exists."""
+        oldentry = self.getGroup(group.Name)
+        if oldentry.Exists :
+            return oldentry # we return the existing entry
         gname = self.userCharsetToDatabase(group.Name)
         newfields = { 
@@ -1014,5 +1025,6 @@
             dn = "%s=%s,%s" % (self.info["grouprdn"], gname, self.info["groupbase"])
             self.doAdd(dn, fields)
-        return self.getGroup(group.Name)
+        group.isDirty = False
+        return None # the entry created doesn't need further modification
         
     def addUserToGroup(self, user, group) :    
Index: pykota/trunk/pykota/storages/sqlitestorage.py
===================================================================
--- pykota/trunk/pykota/storages/sqlitestorage.py (revision 2741)
+++ pykota/trunk/pykota/storages/sqlitestorage.py (revision 2773)
@@ -121,4 +121,5 @@
             self.cursor.execute(query)
         except self.database.Error, msg :    
+            self.tool.logdebug("Query failed : %s" % repr(msg))
             raise PyKotaStorageError, str(msg)
         else :    
Index: pykota/trunk/pykota/storage.py
===================================================================
--- pykota/trunk/pykota/storage.py (revision 2735)
+++ pykota/trunk/pykota/storage.py (revision 2773)
@@ -67,4 +67,5 @@
         self.OverCharge = 1.0
         self.Payments = [] # TODO : maybe handle this smartly for SQL, for now just don't retrieve them
+        self.PaymentsBacklog = []
         
     def consumeAccountBalance(self, amount) :     
@@ -78,6 +79,14 @@
         self.AccountBalance = balance
         self.LifeTimePaid = lifetimepaid
-        self.parent.writeNewPayment(self, diff, comment)
-        self.isDirty = True
+        if diff :
+            self.PaymentsBacklog.append((diff, comment))
+        self.isDirty = True
+        
+    def save(self) :    
+        """Saves an user and flush its payments backlog."""
+        for (value, comment) in self.PaymentsBacklog :
+            self.parent.writeNewPayment(self, value, comment)
+        self.PaymentsBacklog = []    
+        StorageObject.save(self)    
         
     def setLimitBy(self, limitby) :    
Index: pykota/trunk/bin/pkusers
===================================================================
--- pykota/trunk/bin/pkusers (revision 2765)
+++ pykota/trunk/bin/pkusers (revision 2773)
@@ -31,4 +31,5 @@
 
 from pykota.tool import PyKotaTool, PyKotaToolError, PyKotaCommandLineError, crashed, N_
+from pykota.storage import StorageUser, StorageGroup
 
 __doc__ = N_("""pkusers v%(__version__)s (c) %(__years__)s %(__author__)s
@@ -158,4 +159,33 @@
 class PKUsers(PyKotaTool) :        
     """A class for a users and users groups manager."""
+    def modifyEntry(self, entry, groups, limitby, description, overcharge=None, balance=None, balancevalue=None, comment=None, email=None) :
+        """Modifies an entry."""
+        if description is not None : # NB : "" is allowed !
+            entry.setDescription(description)
+        if limitby :    
+            entry.setLimitBy(limitby)
+        if not groups :
+            if email :
+                entry.Email = email
+            if overcharge is not None : # NB : 0 is allowed !     
+                entry.setOverChargeFactor(overcharge)
+            if balance :
+                if balance.startswith("+") or balance.startswith("-") :
+                    newbalance = float(entry.AccountBalance or 0.0) + balancevalue
+                    newlifetimepaid = float(entry.LifeTimePaid or 0.0) + balancevalue
+                    entry.setAccountBalance(newbalance, newlifetimepaid, comment)
+                else :
+                    diff = balancevalue - float(entry.AccountBalance or 0.0)
+                    newlifetimepaid = float(entry.LifeTimePaid or 0.0) + diff
+                    entry.setAccountBalance(balancevalue, newlifetimepaid, comment)
+                    
+    def manageUsersGroups(self, ugroups, user, remove) :        
+        """Manage user group membership."""
+        for ugroup in ugroups :
+            if remove :
+                ugroup.delUserFromGroup(user)
+            else :
+                ugroup.addUserToGroup(user)
+                
     def main(self, names, options) :
         """Manage users or groups."""
@@ -163,192 +193,178 @@
         suffix = (options["groups"] and "Group") or "User"        
         
-        if options["delete"] :    
-            self.display("%s...\n" % _("Deletion"))
-            todelete = getattr(self.storage, "getMatching%ss" % suffix)(",".join(names))
-            nbtotal = len(todelete)
-            for i in range(nbtotal) :
-                entry = todelete[i]
-                if entry.Exists :
-                    entry.delete()
-                percent = 100.0 * float(i) / float(nbtotal)
-                self.display("\r%.02f%%" % percent)
+        if not options["add"] :
+            if not options["list"] :
+                self.display(_("Extracting datas..."))
+            if not names :      # NB : can't happen for --delete because it's catched earlier
+                names = ["*"]
+            entries = getattr(self.storage, "getMatching%ss" % suffix)(",".join(names))
+            if not entries :
+                raise PyKotaCommandLineError, _("There's no %s matching %s") % (_(suffix.lower()), " ".join(names))
+                
+        if options["list"] :
+            if suffix == "User" :
+                maildomain = self.config.getMailDomain()
+                smtpserver = self.config.getSMTPServer()
+                for entry in entries :
+                    email = entry.Email
+                    if not email :
+                        if maildomain :     
+                            email = "%s@%s" % (entry.Name, maildomain)
+                        elif smtpserver :    
+                            email = "%s@%s" % (entry.Name, smtpserver)
+                        else :    
+                            email = "%s@%s" % (entry.Name, "localhost")
+                    msg = "%s - <%s>" % (entry.Name, email)
+                    if entry.Description :
+                        msg += " - %s" % entry.Description
+                    print msg    
+                    print "    %s" % (_("Limited by : %s") % entry.LimitBy)
+                    print "    %s" % (_("Account balance : %.2f") % (entry.AccountBalance or 0.0))
+                    print "    %s" % (_("Total paid so far : %.2f") % (entry.LifeTimePaid or 0.0))
+                    print "    %s" % (_("Overcharging factor : %.2f") % entry.OverCharge)
+                    print
+            else :    
+                for entry in entries :
+                    msg = "%s" % entry.Name
+                    if entry.Description :
+                        msg += " - %s" % entry.Description
+                    print msg    
+                    print "    %s" % (_("Limited by : %s") % entry.LimitBy)
+                    print "    %s" % (_("Group balance : %.2f") % (entry.AccountBalance or 0.0))
+                    print "    %s" % (_("Total paid so far : %.2f") % (entry.LifeTimePaid or 0.0))
+                    print
+        elif options["delete"] :    
+            self.display("\n%s..." % _("Deletion"))
+            getattr(self.storage, "deleteMany%ss" % suffix)(entries)
+            self.display("\n")
         else :
-            if options["add"] :    
-                self.display("%s...\n" % _("Creation"))
-                rejectunknown = self.config.getRejectUnknown()    
-                entries = []
-                nbtotal = len(names)
-                for i in range(nbtotal) :
-                    ename = names[i]
-                    email = ""
-                    if not options["groups"] :
-                        splitname = ename.split('/', 1)     # username/email
-                        if len(splitname) == 1 :
-                            splitname.append("")
-                        (ename, email) = splitname
-                        if email and (email.count('@') != 1) :
-                            self.printInfo(_("Invalid email address %s") % email)
-                            email = ""
-                    entry = getattr(self.storage, "get%s" % suffix)(ename)
-                    if entry.Exists :
-                        if options["skipexisting"] :
-                            self.printInfo(_("%s %s already exists, skipping.") % (suffix, entry.Name))
-                        else :    
-                            self.printInfo(_("%s %s already exists, will be modified.") % (suffix, entry.Name))
-                            entries.append(entry)
-                    else :
-                        if self.isValidName(entry.Name) :
+            limitby = options["limitby"]
+            if limitby :
+                limitby = limitby.strip().lower()
+            if limitby :
+                if limitby not in ('quota', 'balance', 'noquota', \
+                                            'noprint', 'nochange') :
+                    raise PyKotaCommandLineError, _("Invalid limitby value %s") % options["limitby"]
+                if (limitby in ('nochange', 'noprint')) and options["groups"] :    
+                    raise PyKotaCommandLineError, _("Invalid limitby value %s") % options["limitby"]
+                
+            overcharge = options["overcharge"]
+            if overcharge :
+                try :
+                    overcharge = float(overcharge.strip())
+                except (ValueError, AttributeError) :    
+                    raise PyKotaCommandLineError, _("Invalid overcharge value %s") % options["overcharge"]
+                    
+            balance = options["balance"]
+            if balance :
+                balance = balance.strip()
+                try :
+                    balancevalue = float(balance)
+                except ValueError :    
+                    raise PyKotaCommandLineError, _("Invalid balance value %s") % options["balance"]
+                
+            if options["ingroups"] :
+                usersgroups = self.storage.getMatchingGroups(options["ingroups"])
+                if not usersgroups :
+                    raise PyKotaCommandLineError, _("There's no users group matching %s") % " ".join(options["ingroups"].split(','))
+            else :         
+                usersgroups = []
+                    
+            description = options["description"]
+            if description :
+                description = options["description"].strip()
+                
+            comment = options["comment"]
+            if comment :
+                comment = options["comment"].strip()
+            skipexisting = options["skipexisting"]    
+            groups = options["groups"]
+            remove = options["remove"]
+            self.storage.beginTransaction()
+            try :    
+                if options["add"] :    
+                    self.display("%s...\n" % _("Creation"))
+                    rejectunknown = self.config.getRejectUnknown()    
+                    nbtotal = len(names)
+                    for i in range(nbtotal) :
+                        ename = names[i]
+                        email = None
+                        if not groups :
+                            splitname = ename.split('/', 1)     # username/email
+                            if len(splitname) == 1 :
+                                splitname.append("")
+                            (ename, email) = splitname
+                            if email and (email.count('@') != 1) :
+                                raise PyKotaCommandLineError, _("Invalid email address %s") % email
+                        if self.isValidName(ename) :
                             reject = 0
                             if rejectunknown :
-                                if options["groups"] :
+                                if groups :
                                     try :
-                                        grp.getgrnam(entry.Name)
+                                        grp.getgrnam(ename)
                                     except KeyError :    
-                                        self.printInfo(_("Unknown group %s") % entry.Name, "error")
+                                        self.printInfo(_("Unknown group %s") % ename, "error")
                                         reject = 1
                                 else :    
                                     try :
-                                        pwd.getpwnam(entry.Name)
+                                        pwd.getpwnam(ename)
                                     except KeyError :    
-                                        self.printInfo(_("Unknown user %s") % entry.Name, "error")
+                                        self.printInfo(_("Unknown user %s") % ename, "error")
                                         reject = 1
                             if not reject :        
-                                if email :
-                                    entry.Email = email
-                                entry = getattr(self.storage, "add%s" % suffix)(entry)
-                                entries.append(entry)
+                                entry = globals()["Storage%s" % suffix](self.storage, ename)
+                                if groups :
+                                    self.modifyEntry(entry, groups, limitby, \
+                                                     description)
+                                else :    
+                                    self.modifyEntry(entry, groups, limitby, \
+                                                     description, overcharge,\
+                                                     balance, balancevalue, \
+                                                     comment, email)
+                                oldentry = getattr(self.storage, "add%s" % suffix)(entry)
+                                if oldentry is not None :
+                                    if skipexisting :
+                                        self.printInfo(_("%s %s already exists, skipping.") % (_(suffix), ename))
+                                    else :    
+                                        self.printInfo(_("%s %s already exists, will be modified.") % (_(suffix), ename))
+                                        if groups :
+                                            self.modifyEntry(oldentry, groups, \
+                                                     limitby, description)
+                                        else :
+                                            self.modifyEntry(oldentry, groups, limitby, \
+                                                     description, overcharge,\
+                                                     balance, balancevalue, \
+                                                     comment, email)
+                                        oldentry.save()
+                                        if not groups :
+                                            self.manageUsersGroups(usersgroups, oldentry, remove)
+                                elif usersgroups and not groups :
+                                    self.manageUsersGroups(usersgroups, \
+                                                           self.storage.getUser(ename), \
+                                                           remove)
+                        else :
+                            raise PyKotaCommandLineError, _("Invalid name %s") % ename
+                        percent = 100.0 * float(i) / float(nbtotal)
+                        self.display("\r%.02f%%" % percent)
+                else :
+                    self.display("\n%s...\n" % _("Modification"))
+                    nbtotal = len(entries)
+                    for i in range(nbtotal) :        
+                        entry = entries[i]
+                        if groups :
+                            self.modifyEntry(entry, groups, limitby, description)
                         else :    
-                            if options["groups"] :
-                                self.printInfo(_("Invalid group name %s") % entry.Name)
-                            else :    
-                                self.printInfo(_("Invalid user name %s") % entry.Name)
-                                
-                    percent = 100.0 * float(i) / float(nbtotal)
-                    self.display("\r%.02f%%" % percent)
-                self.done()
-            else :        
-                entries = getattr(self.storage, "getMatching%ss" % suffix)(",".join(names))
-                if not entries :
-                    raise PyKotaCommandLineError, _("There's no %s matching %s") % (suffix.lower(), " ".join(names))
-                        
-            if options["list"] :
-                if suffix == "User" :
-                    maildomain = self.config.getMailDomain()
-                    smtpserver = self.config.getSMTPServer()
-                    for entry in entries :
-                        if entry.Exists :
-                            email = entry.Email
-                            if not email :
-                                if maildomain :     
-                                    email = "%s@%s" % (entry.Name, maildomain)
-                                elif smtpserver :    
-                                    email = "%s@%s" % (entry.Name, smtpserver)
-                                else :    
-                                    email = "%s@%s" % (entry.Name, "localhost")
-                            msg = "%s - <%s>" % (entry.Name, email)
-                            if entry.Description :
-                                msg += " - %s" % entry.Description
-                            print msg    
-                            print "    %s" % (_("Limited by : %s") % entry.LimitBy)
-                            print "    %s" % (_("Account balance : %.2f") % (entry.AccountBalance or 0.0))
-                            print "    %s" % (_("Total paid so far : %.2f") % (entry.LifeTimePaid or 0.0))
-                            print "    %s" % (_("Overcharging factor : %.2f") % entry.OverCharge)
-                            print
-                else :    
-                    for entry in entries :
-                        if entry.Exists :
-                            msg = "%s" % entry.Name
-                            if entry.Description :
-                                msg += " - %s" % entry.Description
-                            print msg    
-                            print "    %s" % (_("Limited by : %s") % entry.LimitBy)
-                            print "    %s" % (_("Group balance : %.2f") % (entry.AccountBalance or 0.0))
-                            print "    %s" % (_("Total paid so far : %.2f") % (entry.LifeTimePaid or 0.0))
-                            print
-            else :
-                self.display("%s...\n" % _("Modification"))
-                
-                limitby = options["limitby"]
-                if limitby :
-                    limitby = limitby.strip().lower()
-                if limitby :
-                    if limitby not in ('quota', 'balance', 'noquota', \
-                                                'noprint', 'nochange') :
-                        raise PyKotaCommandLineError, _("Invalid limitby value %s") % options["limitby"]
-                    if (limitby in ('nochange', 'noprint')) and options["groups"] :    
-                        raise PyKotaCommandLineError, _("Invalid limitby value %s") % options["limitby"]
-                    
-                overcharge = options["overcharge"]
-                if overcharge :
-                    try :
-                        overcharge = float(overcharge.strip())
-                    except (ValueError, AttributeError) :    
-                        raise PyKotaCommandLineError, _("Invalid overcharge value %s") % options["overcharge"]
-                        
-                balance = options["balance"]
-                if balance :
-                    balance = balance.strip()
-                    try :
-                        balancevalue = float(balance)
-                    except ValueError :    
-                        raise PyKotaCommandLineError, _("Invalid balance value %s") % options["balance"]
-                    
-                if options["ingroups"] :
-                    usersgroups = self.storage.getMatchingGroups(options["ingroups"])
-                    if not usersgroups :
-                        raise PyKotaCommandLineError, _("There's no users group matching %s") % " ".join(options["ingroups"].split(','))
-                else :         
-                    usersgroups = []
-                        
-                description = options["description"]
-                if description :
-                    description = options["description"].strip()
-                    
-                comment = options["comment"]
-                if comment :
-                    comment = options["comment"].strip()
-                    
-                nbtotal = len(entries)
-                for i in range(nbtotal) :        
-                    entry = entries[i]
-                    
-                    # We need a transaction because we may have to create a record
-                    # in the payments' table at the same time we modify the user's balance.
-                    self.storage.beginTransaction()
-                    try :
-                        if description is not None : # NB : "" is allowed !
-                            entry.setDescription(description)
-                        if limitby :    
-                            entry.setLimitBy(limitby)
-                            
-                        if suffix == "User" :    
-                            if overcharge is not None : # NB : 0 is allowed !     
-                                entry.setOverChargeFactor(overcharge)
-                        
-                        if suffix == "User" :
-                            if balance :
-                                if balance.startswith("+") or balance.startswith("-") :
-                                    newbalance = float(entry.AccountBalance or 0.0) + balancevalue
-                                    newlifetimepaid = float(entry.LifeTimePaid or 0.0) + balancevalue
-                                    entry.setAccountBalance(newbalance, newlifetimepaid, comment)
-                                else :
-                                    diff = balancevalue - float(entry.AccountBalance or 0.0)
-                                    newlifetimepaid = float(entry.LifeTimePaid or 0.0) + diff
-                                    entry.setAccountBalance(balancevalue, newlifetimepaid, comment)
-                                            
-                            for ugroup in usersgroups :
-                                if options["remove"] :
-                                    ugroup.delUserFromGroup(entry)
-                                else :
-                                    ugroup.addUserToGroup(entry)
+                            self.modifyEntry(entry, groups, limitby, description, \
+                                             overcharge, balance, balancevalue, \
+                                             comment)
+                            self.manageUsersGroups(usersgroups, entry, remove)                
                         entry.save()    
-                    except :
-                        self.storage.rollbackTransaction()
-                        raise
-                    else :    
-                        self.storage.commitTransaction()
-
-                    percent = 100.0 * float(i) / float(nbtotal)
-                    self.display("\r%.02f%%" % percent)
+                        percent = 100.0 * float(i) / float(nbtotal)
+                        self.display("\r%.02f%%" % percent)
+            except :                    
+                self.storage.rollbackTransaction()
+                raise
+            else :    
+                self.storage.commitTransaction()
                                 
         if not options["list"] :                
Index: pykota/trunk/NEWS
===================================================================
--- pykota/trunk/NEWS (revision 2768)
+++ pykota/trunk/NEWS (revision 2773)
@@ -22,7 +22,8 @@
 PyKota NEWS :
        
-    - 1.24alpha15 (2006-03-01) :
-        
-        - optimized pkbcodes and pkprinters using edpykota as the model.
+    - 1.24alpha15 (2006-03-02) :
+        
+        - optimized pkbcodes, pkprinters and pkusers using edpykota as 
+          the model.
         
         - 'edpykota --list' and 'pkusers --list' are now authorized 
