Index: pykota/trunk/pykota/storages/ldapstorage.py
===================================================================
--- pykota/trunk/pykota/storages/ldapstorage.py (revision 3411)
+++ pykota/trunk/pykota/storages/ldapstorage.py (revision 3413)
@@ -8,10 +8,10 @@
 # the Free Software Foundation, either version 3 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, see <http://www.gnu.org/licenses/>.
@@ -23,6 +23,6 @@
 """This module defines a class to access to an LDAP database backend.
 
-My IANA assigned number, for 
-"Conseil Internet & Logiciels Libres, Jerome Alet" 
+My IANA assigned number, for
+"Conseil Internet & Logiciels Libres, Jerome Alet"
 is 16868. Use this as a base to extend the LDAP schema.
 """
@@ -42,21 +42,21 @@
                            StorageJob, StorageLastJob, StorageUserPQuota, \
                            StorageGroupPQuota, StorageBillingCode
-                           
-from pykota.utils import *                           
+
+from pykota.utils import *
 
 try :
     import ldap
     import ldap.modlist
-except ImportError :    
+except ImportError :
     raise PyKotaStorageError, "This python version (%s) doesn't seem to have the python-ldap module installed correctly." % sys.version.split()[0]
-else :    
+else :
     try :
         from ldap.cidict import cidict
-    except ImportError :    
+    except ImportError :
         import UserDict
         sys.stderr.write("ERROR: PyKota requires a newer version of python-ldap. Workaround activated. Please upgrade python-ldap !\n")
         class cidict(UserDict.UserDict) :
             pass # Fake it all, and don't care for case insensitivity : users who need it will have to upgrade.
-    
+
 class Storage(BaseStorage) :
     def __init__(self, pykotatool, host, dbname, user, passwd) :
@@ -68,6 +68,6 @@
         self.savedpasswd = passwd
         self.secondStageInit()
-        
-    def secondStageInit(self) :    
+
+    def secondStageInit(self) :
         """Second stage initialisation."""
         BaseStorage.__init__(self, self.savedtool)
@@ -77,5 +77,5 @@
             try :
                 self.tool.logdebug("Trying to open database (host=%s, dbname=%s, user=%s)..." % (self.savedhost, self.saveddbname, self.saveduser))
-                self.database = ldap.initialize(self.savedhost) 
+                self.database = ldap.initialize(self.savedhost)
                 if self.info["ldaptls"] :
                     # we want TLS
@@ -85,15 +85,15 @@
                 self.database.simple_bind_s(self.saveduser, self.savedpasswd)
                 self.basedn = self.saveddbname
-            except ldap.SERVER_DOWN :    
+            except ldap.SERVER_DOWN :
                 message = "LDAP backend for PyKota seems to be down !"
                 self.tool.printInfo("%s" % message, "error")
                 self.tool.printInfo("Trying again in 2 seconds...", "warn")
                 time.sleep(2)
-            except ldap.LDAPError :    
+            except ldap.LDAPError :
                 message = "Unable to connect to LDAP server %s as %s." % (self.savedhost, self.saveduser)
                 self.tool.printInfo("%s" % message, "error")
                 self.tool.printInfo("Trying again in 2 seconds...", "warn")
                 time.sleep(2)
-            else :    
+            else :
                 self.useldapcache = self.tool.config.getLDAPCache()
                 if self.useldapcache :
@@ -103,7 +103,7 @@
                 self.tool.logdebug("Database opened (host=%s, dbname=%s, user=%s)" % (self.savedhost, self.saveddbname, self.saveduser))
                 return # All is fine here.
-        raise PyKotaStorageError, message         
-            
-    def close(self) :    
+        raise PyKotaStorageError, message
+
+    def close(self) :
         """Closes the database connection."""
         if not self.closed :
@@ -111,13 +111,13 @@
             self.closed = 1
             self.tool.logdebug("Database closed.")
-        
-    def genUUID(self) :    
+
+    def genUUID(self) :
         """Generates an unique identifier.
-        
+
            TODO : this one is not unique accross several print servers, but should be sufficient for testing.
         """
         return md5.md5("%s-%s" % (time.time(), random.random())).hexdigest()
-        
-    def normalizeFields(self, fields) :    
+
+    def normalizeFields(self, fields) :
         """Ensure all items are lists."""
         for (k, v) in fields.items() :
@@ -125,20 +125,20 @@
                 if not v :
                     del fields[k]
-                else :    
+                else :
                     fields[k] = [ v ]
-        return fields        
-        
-    def beginTransaction(self) :    
+        return fields
+
+    def beginTransaction(self) :
         """Starts a transaction."""
         self.tool.logdebug("Transaction begins... WARNING : No transactions in LDAP !")
-        
-    def commitTransaction(self) :    
+
+    def commitTransaction(self) :
         """Commits a transaction."""
         self.tool.logdebug("Transaction committed. WARNING : No transactions in LDAP !")
-        
-    def rollbackTransaction(self) :     
+
+    def rollbackTransaction(self) :
         """Rollbacks a transaction."""
         self.tool.logdebug("Transaction aborted. WARNING : No transaction in LDAP !")
-        
+
     def doSearch(self, key, fields=None, base="", scope=ldap.SCOPE_SUBTREE, flushcache=0) :
         """Does an LDAP search query."""
@@ -151,10 +151,10 @@
                     # retrieve ALL user defined attributes ("*")
                     # + the createTimestamp attribute, needed by job history
-                    # 
+                    #
                     # This may not work with all LDAP servers
-                    # but works at least in OpenLDAP (2.1.25) 
+                    # but works at least in OpenLDAP (2.1.25)
                     # and iPlanet Directory Server (5.1 SP3)
-                    fields = ["*", "createTimestamp"]         
-                    
+                    fields = ["*", "createTimestamp"]
+
                 if self.useldapcache and (not flushcache) and (scope == ldap.SCOPE_BASE) and self.ldapcache.has_key(base) :
                     entry = self.ldapcache[base]
@@ -164,7 +164,7 @@
                     self.querydebug("QUERY : Filter : %s, BaseDN : %s, Scope : %s, Attributes : %s" % (key, base, scope, fields))
                     result = self.database.search_s(base, scope, key, fields)
-            except ldap.NO_SUCH_OBJECT, msg :        
+            except ldap.NO_SUCH_OBJECT, msg :
                 raise PyKotaStorageError, (_("Search base %s doesn't seem to exist. Probable misconfiguration. Please double check /etc/pykota/pykota.conf : %s") % (base, msg))
-            except ldap.LDAPError, msg :    
+            except ldap.LDAPError, msg :
                 message = (_("Search for %s(%s) from %s(scope=%s) returned no answer.") % (key, fields, base, scope)) + " : %s" % msg
                 self.tool.printInfo("LDAP error : %s" % message, "error")
@@ -172,5 +172,5 @@
                 self.close()
                 self.secondStageInit()
-            else :     
+            else :
                 self.querydebug("QUERY : Result : %s" % result)
                 result = [ (dn, cidict(attrs)) for (dn, attrs) in result ]
@@ -181,5 +181,5 @@
                 return result
         raise PyKotaStorageError, message
-            
+
     def doAdd(self, dn, fields) :
         """Adds an entry in the LDAP directory."""
@@ -192,5 +192,5 @@
                 self.querydebug("%s" % entry)
                 self.database.add_s(dn, entry)
-            except ldap.ALREADY_EXISTS, msg :        
+            except ldap.ALREADY_EXISTS, msg :
                 raise PyKotaStorageError, "Entry %s already exists : %s" % (dn, msg)
             except ldap.LDAPError, msg :
@@ -206,5 +206,5 @@
                 return dn
         raise PyKotaStorageError, message
-            
+
     def doDelete(self, dn) :
         """Deletes an entry from the LDAP directory."""
@@ -214,5 +214,5 @@
                 self.querydebug("QUERY : Delete(%s)" % dn)
                 self.database.delete_s(dn)
-            except ldap.NO_SUCH_OBJECT :    
+            except ldap.NO_SUCH_OBJECT :
                 self.tool.printInfo("Entry %s was already missing before we deleted it. This **MAY** be normal." % dn, "info")
             except ldap.LDAPError, msg :
@@ -222,14 +222,14 @@
                 self.close()
                 self.secondStageInit()
-            else :    
+            else :
                 if self.useldapcache :
                     try :
                         self.querydebug("LDAP cache del %s" % dn)
                         del self.ldapcache[dn]
-                    except KeyError :    
+                    except KeyError :
                         pass
-                return        
+                return
         raise PyKotaStorageError, message
-            
+
     def doModify(self, dn, fields, ignoreold=1, flushcache=0) :
         """Modifies an entry in the LDAP directory."""
@@ -246,8 +246,8 @@
                             if k != "createTimestamp" :
                                 oldentry[k] = v
-                    else :    
+                    else :
                         self.querydebug("LDAP cache miss %s" % dn)
                         oldentry = self.doSearch("objectClass=*", base=dn, scope=ldap.SCOPE_BASE)[0][1]
-                else :        
+                else :
                     oldentry = self.doSearch("objectClass=*", base=dn, scope=ldap.SCOPE_BASE, flushcache=flushcache)[0][1]
                 for (k, v) in fields.items() :
@@ -255,10 +255,10 @@
                         try :
                             oldvalue = v["convert"](oldentry.get(k, [0])[0])
-                        except ValueError :    
+                        except ValueError :
                             self.querydebug("Error converting %s with %s(%s)" % (oldentry.get(k), k, v))
                             oldvalue = 0
                         if v["operator"] == '+' :
                             newvalue = oldvalue + v["value"]
-                        else :    
+                        else :
                             newvalue = oldvalue - v["value"]
                         fields[k] = str(newvalue)
@@ -288,15 +288,15 @@
                             try :
                                 del cachedentry[mtyp]
-                            except KeyError :    
+                            except KeyError :
                                 pass
                     self.querydebug("LDAP cache update %s => %s" % (dn, cachedentry))
                 return dn
         raise PyKotaStorageError, message
-            
+
     def filterNames(self, records, attribute, patterns=None) :
         """Returns a list of 'attribute' from a list of records.
-        
+
            Logs any missing attribute.
-        """   
+        """
         result = []
         for (dn, record) in records :
@@ -309,11 +309,11 @@
                     if (not isinstance(patterns, type([]))) and (not isinstance(patterns, type(()))) :
                         patterns = [ patterns ]
-                    if self.tool.matchString(attrval, patterns) :   
+                    if self.tool.matchString(attrval, patterns) :
                         result.append(attrval)
-                else :    
+                else :
                     result.append(attrval)
-        return result        
-                
-    def getAllBillingCodes(self, billingcode=None) :    
+        return result
+
+    def getAllBillingCodes(self, billingcode=None) :
         """Extracts all billing codes or only the billing codes matching the optional parameter."""
         ldapfilter = "objectClass=pykotaBilling"
@@ -321,8 +321,8 @@
         if result :
             return self.filterNames(result, "pykotaBillingCode", billingcode)
-        else :    
+        else :
             return []
-        
-    def getAllPrintersNames(self, printername=None) :    
+
+    def getAllPrintersNames(self, printername=None) :
         """Extracts all printer names or only the printers' names matching the optional parameter."""
         ldapfilter = "objectClass=pykotaPrinter"
@@ -330,8 +330,8 @@
         if result :
             return self.filterNames(result, "pykotaPrinterName", printername)
-        else :    
+        else :
             return []
-        
-    def getAllUsersNames(self, username=None) :    
+
+    def getAllUsersNames(self, username=None) :
         """Extracts all user names or only the users' names matching the optional parameter."""
         ldapfilter = "objectClass=pykotaAccount"
@@ -339,8 +339,8 @@
         if result :
             return self.filterNames(result, "pykotaUserName", username)
-        else :    
+        else :
             return []
-        
-    def getAllGroupsNames(self, groupname=None) :    
+
+    def getAllGroupsNames(self, groupname=None) :
         """Extracts all group names or only the groups' names matching the optional parameter."""
         ldapfilter = "objectClass=pykotaGroup"
@@ -348,13 +348,13 @@
         if result :
             return self.filterNames(result, "pykotaGroupName", groupname)
-        else :    
+        else :
             return []
-        
+
     def getUserNbJobsFromHistory(self, user) :
         """Returns the number of jobs the user has in history."""
         result = self.doSearch("(&(pykotaUserName=%s)(objectClass=pykotaJob))" % unicodeToDatabase(user.Name), None, base=self.info["jobbase"])
         return len(result)
-        
-    def getUserFromBackend(self, username) :    
+
+    def getUserFromBackend(self, username) :
         """Extracts user information given its name."""
         user = StorageUser(self, username)
@@ -377,7 +377,7 @@
                     if user.AccountBalance[0].upper() == "NONE" :
                         user.AccountBalance = None
-                    else :    
+                    else :
                         user.AccountBalance = float(user.AccountBalance[0])
-                user.AccountBalance = user.AccountBalance or 0.0        
+                user.AccountBalance = user.AccountBalance or 0.0
                 user.LifeTimePaid = fields.get("pykotaLifeTimePaid")
                 user.OverCharge = float(fields.get("pykotaOverCharge", [1.0])[0])
@@ -385,7 +385,7 @@
                     if user.LifeTimePaid[0].upper() == "NONE" :
                         user.LifeTimePaid = None
-                    else :    
+                    else :
                         user.LifeTimePaid = float(user.LifeTimePaid[0])
-                user.LifeTimePaid = user.LifeTimePaid or 0.0        
+                user.LifeTimePaid = user.LifeTimePaid or 0.0
                 user.Payments = []
                 for payment in fields.get("pykotaPayments", []) :
@@ -396,13 +396,13 @@
                         (date, amount) = payment.split(" # ")
                         description = ""
-                    else :    
+                    else :
                         description = databaseToUnicode(base64.decodestring(description))
-                    if amount.endswith(" #") :    
+                    if amount.endswith(" #") :
                         amount = amount[:-2] # TODO : should be catched earlier, the bug is above I think
                     user.Payments.append((date, float(amount), description))
             user.Exists = True
         return user
-       
-    def getGroupFromBackend(self, groupname) :    
+
+    def getGroupFromBackend(self, groupname) :
         """Extracts group information given its name."""
         group = StorageGroup(self, groupname)
@@ -423,6 +423,6 @@
             group.Exists = True
         return group
-       
-    def getPrinterFromBackend(self, printername) :        
+
+    def getPrinterFromBackend(self, printername) :
         """Extracts printer information given its name : returns first matching printer."""
         printer = StoragePrinter(self, printername)
@@ -447,9 +447,9 @@
                 printer.PassThrough = 0
             printer.uniqueMember = fields.get("uniqueMember", [])
-            printer.Description = databaseToUnicode(fields.get("description", [""])[0]) 
+            printer.Description = databaseToUnicode(fields.get("description", [""])[0])
             printer.Exists = True
-        return printer    
-        
-    def getUserPQuotaFromBackend(self, user, printer) :        
+        return printer
+
+    def getUserPQuotaFromBackend(self, user, printer) :
         """Extracts a user print quota."""
         userpquota = StorageUserPQuota(self, user, printer)
@@ -457,5 +457,5 @@
             if self.info["userquotabase"].lower() == "user" :
                 base = user.ident
-            else :    
+            else :
                 base = self.info["userquotabase"]
             result = self.doSearch("(&(objectClass=pykotaUserPQuota)(pykotaUserName=%s)(pykotaPrinterName=%s))" % \
@@ -473,5 +473,5 @@
                     if userpquota.SoftLimit[0].upper() == "NONE" :
                         userpquota.SoftLimit = None
-                    else :    
+                    else :
                         userpquota.SoftLimit = int(userpquota.SoftLimit[0])
                 userpquota.HardLimit = fields.get("pykotaHardLimit")
@@ -479,11 +479,11 @@
                     if userpquota.HardLimit[0].upper() == "NONE" :
                         userpquota.HardLimit = None
-                    elif userpquota.HardLimit is not None :    
+                    elif userpquota.HardLimit is not None :
                         userpquota.HardLimit = int(userpquota.HardLimit[0])
                 userpquota.DateLimit = fields.get("pykotaDateLimit")
                 if userpquota.DateLimit is not None :
-                    if userpquota.DateLimit[0].upper() == "NONE" : 
+                    if userpquota.DateLimit[0].upper() == "NONE" :
                         userpquota.DateLimit = None
-                    else :    
+                    else :
                         userpquota.DateLimit = userpquota.DateLimit[0]
                 userpquota.MaxJobSize = fields.get("pykotaMaxJobSize")
@@ -491,10 +491,10 @@
                     if userpquota.MaxJobSize[0].upper() == "NONE" :
                         userpquota.MaxJobSize = None
-                    else :    
+                    else :
                         userpquota.MaxJobSize = int(userpquota.MaxJobSize[0])
                 userpquota.Exists = True
         return userpquota
-        
-    def getGroupPQuotaFromBackend(self, group, printer) :        
+
+    def getGroupPQuotaFromBackend(self, group, printer) :
         """Extracts a group print quota."""
         grouppquota = StorageGroupPQuota(self, group, printer)
@@ -502,5 +502,5 @@
             if self.info["groupquotabase"].lower() == "group" :
                 base = group.ident
-            else :    
+            else :
                 base = self.info["groupquotabase"]
             result = self.doSearch("(&(objectClass=pykotaGroupPQuota)(pykotaGroupName=%s)(pykotaPrinterName=%s))" % \
@@ -515,5 +515,5 @@
                     if grouppquota.SoftLimit[0].upper() == "NONE" :
                         grouppquota.SoftLimit = None
-                    else :    
+                    else :
                         grouppquota.SoftLimit = int(grouppquota.SoftLimit[0])
                 grouppquota.HardLimit = fields.get("pykotaHardLimit")
@@ -521,11 +521,11 @@
                     if grouppquota.HardLimit[0].upper() == "NONE" :
                         grouppquota.HardLimit = None
-                    else :    
+                    else :
                         grouppquota.HardLimit = int(grouppquota.HardLimit[0])
                 grouppquota.DateLimit = fields.get("pykotaDateLimit")
                 if grouppquota.DateLimit is not None :
-                    if grouppquota.DateLimit[0].upper() == "NONE" : 
+                    if grouppquota.DateLimit[0].upper() == "NONE" :
                         grouppquota.DateLimit = None
-                    else :    
+                    else :
                         grouppquota.DateLimit = grouppquota.DateLimit[0]
                 grouppquota.MaxJobSize = fields.get("pykotaMaxJobSize")
@@ -533,5 +533,5 @@
                     if grouppquota.MaxJobSize[0].upper() == "NONE" :
                         grouppquota.MaxJobSize = None
-                    else :    
+                    else :
                         grouppquota.MaxJobSize = int(grouppquota.MaxJobSize[0])
                 grouppquota.PageCounter = 0
@@ -548,11 +548,11 @@
                                           ["pykotaPageCounter", "pykotaLifePageCounter"], base=base)
                 if result :
-                    for userpquota in result :    
+                    for userpquota in result :
                         grouppquota.PageCounter += int(userpquota[1].get("pykotaPageCounter", [0])[0] or 0)
                         grouppquota.LifePageCounter += int(userpquota[1].get("pykotaLifePageCounter", [0])[0] or 0)
                 grouppquota.Exists = True
         return grouppquota
-        
-    def getPrinterLastJobFromBackend(self, printer) :        
+
+    def getPrinterLastJobFromBackend(self, printer) :
         """Extracts a printer's last job information."""
         lastjob = StorageLastJob(self, printer)
@@ -567,26 +567,26 @@
             result = None
             try :
-                result = self.doSearch("objectClass=pykotaJob", [ "pykotaJobSizeBytes", 
-                                                                  "pykotaHostName", 
-                                                                  "pykotaUserName", 
-                                                                  "pykotaPrinterName", 
-                                                                  "pykotaJobId", 
-                                                                  "pykotaPrinterPageCounter", 
-                                                                  "pykotaJobSize", 
-                                                                  "pykotaAction", 
-                                                                  "pykotaJobPrice", 
-                                                                  "pykotaFileName", 
-                                                                  "pykotaTitle", 
-                                                                  "pykotaCopies", 
-                                                                  "pykotaOptions", 
-                                                                  "pykotaBillingCode", 
-                                                                  "pykotaPages", 
-                                                                  "pykotaMD5Sum", 
+                result = self.doSearch("objectClass=pykotaJob", [ "pykotaJobSizeBytes",
+                                                                  "pykotaHostName",
+                                                                  "pykotaUserName",
+                                                                  "pykotaPrinterName",
+                                                                  "pykotaJobId",
+                                                                  "pykotaPrinterPageCounter",
+                                                                  "pykotaJobSize",
+                                                                  "pykotaAction",
+                                                                  "pykotaJobPrice",
+                                                                  "pykotaFileName",
+                                                                  "pykotaTitle",
+                                                                  "pykotaCopies",
+                                                                  "pykotaOptions",
+                                                                  "pykotaBillingCode",
+                                                                  "pykotaPages",
+                                                                  "pykotaMD5Sum",
                                                                   "pykotaPrecomputedJobSize",
                                                                   "pykotaPrecomputedJobPrice",
-                                                                  "createTimestamp" ], 
+                                                                  "createTimestamp" ],
                                                                 base="cn=%s,%s" % (lastjobident, self.info["jobbase"]), scope=ldap.SCOPE_BASE)
-            except PyKotaStorageError :    
-                pass # Last job entry exists, but job probably doesn't exist anymore. 
+            except PyKotaStorageError :
+                pass # Last job entry exists, but job probably doesn't exist anymore.
             if result :
                 fields = result[0][1]
@@ -597,15 +597,15 @@
                 try :
                     lastjob.JobSize = int(fields.get("pykotaJobSize", [0])[0])
-                except ValueError :    
+                except ValueError :
                     lastjob.JobSize = None
-                try :    
+                try :
                     lastjob.JobPrice = float(fields.get("pykotaJobPrice", [0.0])[0])
-                except ValueError :    
+                except ValueError :
                     lastjob.JobPrice = None
                 lastjob.JobAction = databaseToUnicode(fields.get("pykotaAction", [""])[0])
-                lastjob.JobFileName = databaseToUnicode(fields.get("pykotaFileName", [""])[0]) 
-                lastjob.JobTitle = databaseToUnicode(fields.get("pykotaTitle", [""])[0]) 
+                lastjob.JobFileName = databaseToUnicode(fields.get("pykotaFileName", [""])[0])
+                lastjob.JobTitle = databaseToUnicode(fields.get("pykotaTitle", [""])[0])
                 lastjob.JobCopies = int(fields.get("pykotaCopies", [0])[0])
-                lastjob.JobOptions = databaseToUnicode(fields.get("pykotaOptions", [""])[0]) 
+                lastjob.JobOptions = databaseToUnicode(fields.get("pykotaOptions", [""])[0])
                 lastjob.JobHostName = databaseToUnicode(fields.get("pykotaHostName", [""])[0])
                 lastjob.JobSizeBytes = fields.get("pykotaJobSizeBytes", [0L])[0]
@@ -615,9 +615,9 @@
                 try :
                     lastjob.PrecomputedJobSize = int(fields.get("pykotaPrecomputedJobSize", [0])[0])
-                except ValueError :    
+                except ValueError :
                     lastjob.PrecomputedJobSize = None
-                try :    
+                try :
                     lastjob.PrecomputedJobPrice = float(fields.get("pykotaPrecomputedJobPrice", [0.0])[0])
-                except ValueError :    
+                except ValueError :
                     lastjob.PrecomputedJobPrice = None
                 if lastjob.JobTitle == lastjob.JobFileName == lastjob.JobOptions == u"hidden" :
@@ -628,6 +628,6 @@
                 lastjob.Exists = True
         return lastjob
-        
-    def getGroupMembersFromBackend(self, group) :        
+
+    def getGroupMembersFromBackend(self, group) :
         """Returns the group's members list."""
         groupmembers = []
@@ -640,7 +640,7 @@
             for username in result[0][1].get(self.info["groupmembers"], []) :
                 groupmembers.append(self.getUser(databaseToUnicode(username)))
-        return groupmembers        
-        
-    def getUserGroupsFromBackend(self, user) :        
+        return groupmembers
+
+    def getUserGroupsFromBackend(self, user) :
         """Returns the user's groups list."""
         groups = []
@@ -660,5 +660,5 @@
                     if group.LimitBy is not None :
                         group.LimitBy = databaseToUnicode(group.LimitBy[0])
-                    else :    
+                    else :
                         group.LimitBy = u"quota"
                     group.AccountBalance = 0.0
@@ -671,7 +671,7 @@
                     self.cacheEntry("GROUPS", group.Name, group)
                 groups.append(group)
-        return groups        
-        
-    def getParentPrintersFromBackend(self, printer) :    
+        return groups
+
+    def getParentPrintersFromBackend(self, printer) :
         """Get all the printer groups this printer is a member of."""
         pgroups = []
@@ -687,5 +687,5 @@
                         pgroups.append(parentprinter)
         return pgroups
-        
+
     def getMatchingPrinters(self, printerpattern) :
         """Returns the list of all printers for which name matches a certain pattern."""
@@ -699,5 +699,5 @@
             try :
                 patdict = {}.fromkeys(patterns)
-            except AttributeError :    
+            except AttributeError :
                 # Python v2.2 or earlier
                 patdict = {}
@@ -718,10 +718,10 @@
                         printer.PassThrough = 0
                     printer.uniqueMember = fields.get("uniqueMember", [])
-                    printer.Description = databaseToUnicode(fields.get("description", [""])[0]) 
+                    printer.Description = databaseToUnicode(fields.get("description", [""])[0])
                     printer.Exists = True
                     printers.append(printer)
                     self.cacheEntry("PRINTERS", printer.Name, printer)
-        return printers        
-        
+        return printers
+
     def getMatchingUsers(self, userpattern) :
         """Returns the list of all users for which name matches a certain pattern."""
@@ -735,5 +735,5 @@
             try :
                 patdict = {}.fromkeys(patterns)
-            except AttributeError :    
+            except AttributeError :
                 # Python v2.2 or earlier
                 patdict = {}
@@ -747,5 +747,5 @@
                     user.Email = databaseToUnicode(fields.get(self.info["usermail"], [None])[0])
                     user.LimitBy = databaseToUnicode(fields.get("pykotaLimitBy", ["quota"])[0])
-                    user.Description = databaseToUnicode(fields.get("description", [""])[0]) 
+                    user.Description = databaseToUnicode(fields.get("description", [""])[0])
                     uname = unicodeToDatabase(username)
                     result = self.doSearch("(&(objectClass=pykotaAccountBalance)(|(pykotaUserName=%s)(%s=%s)))" % \
@@ -763,14 +763,14 @@
                             if user.AccountBalance[0].upper() == "NONE" :
                                 user.AccountBalance = None
-                            else :    
+                            else :
                                 user.AccountBalance = float(user.AccountBalance[0])
-                        user.AccountBalance = user.AccountBalance or 0.0        
+                        user.AccountBalance = user.AccountBalance or 0.0
                         user.LifeTimePaid = fields.get("pykotaLifeTimePaid")
                         if user.LifeTimePaid is not None :
                             if user.LifeTimePaid[0].upper() == "NONE" :
                                 user.LifeTimePaid = None
-                            else :    
+                            else :
                                 user.LifeTimePaid = float(user.LifeTimePaid[0])
-                        user.LifeTimePaid = user.LifeTimePaid or 0.0        
+                        user.LifeTimePaid = user.LifeTimePaid or 0.0
                         user.Payments = []
                         for payment in fields.get("pykotaPayments", []) :
@@ -781,7 +781,7 @@
                                 (date, amount) = payment.split(" # ")
                                 description = ""
-                            else :    
+                            else :
                                 description = databaseToUnicode(base64.decodestring(description))
-                            if amount.endswith(" #") :    
+                            if amount.endswith(" #") :
                                 amount = amount[:-2] # TODO : should be catched earlier, the bug is above I think
                             user.Payments.append((date, float(amount), description))
@@ -789,6 +789,6 @@
                     users.append(user)
                     self.cacheEntry("USERS", user.Name, user)
-        return users       
-        
+        return users
+
     def getMatchingGroups(self, grouppattern) :
         """Returns the list of all groups for which name matches a certain pattern."""
@@ -802,5 +802,5 @@
             try :
                 patdict = {}.fromkeys(patterns)
-            except AttributeError :    
+            except AttributeError :
                 # Python v2.2 or earlier
                 patdict = {}
@@ -812,7 +812,7 @@
                     group = StorageGroup(self, groupname)
                     group.ident = groupid
-                    group.Name = databaseToUnicode(fields.get("pykotaGroupName", [groupname])[0]) 
+                    group.Name = databaseToUnicode(fields.get("pykotaGroupName", [groupname])[0])
                     group.LimitBy = databaseToUnicode(fields.get("pykotaLimitBy", ["quota"])[0])
-                    group.Description = databaseToUnicode(fields.get("description", [""])[0]) 
+                    group.Description = databaseToUnicode(fields.get("description", [""])[0])
                     group.AccountBalance = 0.0
                     group.LifeTimePaid = 0.0
@@ -825,6 +825,6 @@
                     self.cacheEntry("GROUPS", group.Name, group)
         return groups
-        
-    def getPrinterUsersAndQuotas(self, printer, names=["*"]) :        
+
+    def getPrinterUsersAndQuotas(self, printer, names=["*"]) :
         """Returns the list of users who uses a given printer, along with their quotas."""
         usersandquotas = []
@@ -851,5 +851,5 @@
                     if userpquota.SoftLimit[0].upper() == "NONE" :
                         userpquota.SoftLimit = None
-                    else :    
+                    else :
                         userpquota.SoftLimit = int(userpquota.SoftLimit[0])
                 userpquota.HardLimit = fields.get("pykotaHardLimit")
@@ -857,19 +857,19 @@
                     if userpquota.HardLimit[0].upper() == "NONE" :
                         userpquota.HardLimit = None
-                    elif userpquota.HardLimit is not None :    
+                    elif userpquota.HardLimit is not None :
                         userpquota.HardLimit = int(userpquota.HardLimit[0])
                 userpquota.DateLimit = fields.get("pykotaDateLimit")
                 if userpquota.DateLimit is not None :
-                    if userpquota.DateLimit[0].upper() == "NONE" : 
+                    if userpquota.DateLimit[0].upper() == "NONE" :
                         userpquota.DateLimit = None
-                    else :    
+                    else :
                         userpquota.DateLimit = userpquota.DateLimit[0]
                 userpquota.Exists = True
                 usersandquotas.append((user, userpquota))
                 self.cacheEntry("USERPQUOTAS", "%s@%s" % (user.Name, printer.Name), userpquota)
-        usersandquotas.sort(lambda x, y : cmp(x[0].Name, y[0].Name))            
+        usersandquotas.sort(lambda x, y : cmp(x[0].Name, y[0].Name))
         return usersandquotas
-                
-    def getPrinterGroupsAndQuotas(self, printer, names=["*"]) :        
+
+    def getPrinterGroupsAndQuotas(self, printer, names=["*"]) :
         """Returns the list of groups which uses a given printer, along with their quotas."""
         groupsandquotas = []
@@ -889,7 +889,7 @@
                 grouppquota = self.getGroupPQuota(group, printer)
                 groupsandquotas.append((group, grouppquota))
-        groupsandquotas.sort(lambda x, y : cmp(x[0].Name, y[0].Name))            
+        groupsandquotas.sort(lambda x, y : cmp(x[0].Name, y[0].Name))
         return groupsandquotas
-        
+
     def addPrinter(self, printer) :
         """Adds a printer to the quota storage, returns the old value if it already exists."""
@@ -907,11 +907,11 @@
                    "pykotaPricePerPage" : str(printer.PricePerPage or 0.0),
                    "pykotaPricePerJob" : str(printer.PricePerJob or 0.0),
-                 } 
+                 }
         dn = "%s=%s,%s" % (self.info["printerrdn"], printername, self.info["printerbase"])
         self.doAdd(dn, fields)
         printer.isDirty = False
         return None # the entry created doesn't need further modification
-        
-    def addUser(self, user) :        
+
+    def addUser(self, user) :
         """Adds a user to the quota storage, returns the old value if it already exists."""
         oldentry = self.getUser(user.Name)
@@ -924,6 +924,6 @@
                        "description" : unicodeToDatabase(user.Description or ""),
                        self.info["usermail"] : unicodeToDatabase(user.Email or ""),
-                    }   
-                       
+                    }
+
         mustadd = 1
         if self.info["newuser"].lower() != 'below' :
@@ -943,16 +943,16 @@
                 fields.update({ "pykotaBalance" : str(user.AccountBalance or 0.0),
                                 "pykotaOverCharge" : str(user.OverCharge),
-                                "pykotaLifeTimePaid" : str(user.LifeTimePaid or 0.0), })   
+                                "pykotaLifeTimePaid" : str(user.LifeTimePaid or 0.0), })
                 self.doModify(dn, fields)
                 mustadd = 0
             else :
                 message = _("Unable to find an existing objectClass %s entry with %s=%s to attach pykotaAccount objectClass") % (where, self.info["userrdn"], user.Name)
-                if action.lower() == "warn" :    
+                if action.lower() == "warn" :
                     self.tool.printInfo(_("%s. A new entry will be created instead.") % message, "warn")
                 else : # 'fail' or incorrect setting
                     raise PyKotaStorageError, "%s. Action aborted. Please check your configuration." % message
-                
+
         if mustadd :
-            if self.info["userbase"] == self.info["balancebase"] :            
+            if self.info["userbase"] == self.info["balancebase"] :
                 fields = { self.info["userrdn"] : uname,
                            "objectClass" : ["pykotaObject", "pykotaAccount", "pykotaAccountBalance"],
@@ -960,15 +960,15 @@
                            "pykotaBalance" : str(user.AccountBalance or 0.0),
                            "pykotaOverCharge" : str(user.OverCharge),
-                           "pykotaLifeTimePaid" : str(user.LifeTimePaid or 0.0), 
-                         } 
-            else :             
+                           "pykotaLifeTimePaid" : str(user.LifeTimePaid or 0.0),
+                         }
+            else :
                 fields = { self.info["userrdn"] : uname,
                            "objectClass" : ["pykotaObject", "pykotaAccount"],
                            "cn" : uname,
-                         } 
-            fields.update(newfields)         
+                         }
+            fields.update(newfields)
             dn = "%s=%s,%s" % (self.info["userrdn"], uname, self.info["userbase"])
             self.doAdd(dn, fields)
-            if self.info["userbase"] != self.info["balancebase"] :            
+            if self.info["userbase"] != self.info["balancebase"] :
                 fields = { self.info["balancerdn"] : uname,
                            "objectClass" : ["pykotaObject", "pykotaAccountBalance"],
@@ -976,6 +976,6 @@
                            "pykotaBalance" : str(user.AccountBalance or 0.0),
                            "pykotaOverCharge" : str(user.OverCharge),
-                           "pykotaLifeTimePaid" : str(user.LifeTimePaid or 0.0),  
-                         } 
+                           "pykotaLifeTimePaid" : str(user.LifeTimePaid or 0.0),
+                         }
                 dn = "%s=%s,%s" % (self.info["balancerdn"], uname, self.info["balancebase"])
                 self.doAdd(dn, fields)
@@ -987,6 +987,6 @@
         user.isDirty = False
         return None # the entry created doesn't need further modification
-        
-    def addGroup(self, group) :        
+
+    def addGroup(self, group) :
         """Adds a group to the quota storage, returns the old value if it already exists."""
         oldentry = self.getGroup(group.Name)
@@ -994,9 +994,9 @@
             return oldentry # we return the existing entry
         gname = unicodeToDatabase(group.Name)
-        newfields = { 
+        newfields = {
                       "pykotaGroupName" : gname,
                       "pykotaLimitBy" : unicodeToDatabase(group.LimitBy or u"quota"),
                       "description" : unicodeToDatabase(group.Description or "")
-                    } 
+                    }
         mustadd = 1
         if self.info["newgroup"].lower() != 'below' :
@@ -1018,24 +1018,24 @@
             else :
                 message = _("Unable to find an existing entry to attach pykotaGroup objectclass %s") % group.Name
-                if action.lower() == "warn" :    
+                if action.lower() == "warn" :
                     self.tool.printInfo("%s. A new entry will be created instead." % message, "warn")
                 else : # 'fail' or incorrect setting
                     raise PyKotaStorageError, "%s. Action aborted. Please check your configuration." % message
-                
+
         if mustadd :
             fields = { self.info["grouprdn"] : gname,
                        "objectClass" : ["pykotaObject", "pykotaGroup"],
                        "cn" : gname,
-                     } 
-            fields.update(newfields)         
+                     }
+            fields.update(newfields)
             dn = "%s=%s,%s" % (self.info["grouprdn"], gname, self.info["groupbase"])
             self.doAdd(dn, fields)
         group.isDirty = False
         return None # the entry created doesn't need further modification
-        
-    def addUserToGroup(self, user, group) :    
+
+    def addUserToGroup(self, user, group) :
         """Adds an user to a group."""
         if user.Name not in [u.Name for u in self.getGroupMembers(group)] :
-            result = self.doSearch("objectClass=pykotaGroup", None, base=group.ident, scope=ldap.SCOPE_BASE)    
+            result = self.doSearch("objectClass=pykotaGroup", None, base=group.ident, scope=ldap.SCOPE_BASE)
             if result :
                 fields = result[0][1]
@@ -1045,6 +1045,6 @@
                 self.doModify(group.ident, fields)
                 group.Members.append(user)
-                
-    def delUserFromGroup(self, user, group) :    
+
+    def delUserFromGroup(self, user, group) :
         """Removes an user from a group."""
         if user.Name in [u.Name for u in self.getGroupMembers(group)] :
@@ -1054,5 +1054,5 @@
                 if not fields.has_key(self.info["groupmembers"]) :
                     fields[self.info["groupmembers"]] = []
-                try :    
+                try :
                     fields[self.info["groupmembers"]].remove(unicodeToDatabase(user.Name))
                 except ValueError :
@@ -1061,5 +1061,5 @@
                     self.doModify(group.ident, fields)
                     group.Members.remove(user)
-                
+
     def addUserPQuota(self, upq) :
         """Initializes a user print quota on a printer."""
@@ -1082,13 +1082,13 @@
                    "pykotaWarnCount" : str(upq.WarnCount or 0),
                    "pykotaMaxJobSize" : str(upq.MaxJobSize or 0),
-                 } 
+                 }
         if self.info["userquotabase"].lower() == "user" :
             dn = "cn=%s,%s" % (uuid, upq.User.ident)
-        else :    
+        else :
             dn = "cn=%s,%s" % (uuid, self.info["userquotabase"])
         self.doAdd(dn, fields)
         upq.isDirty = False
         return None # the entry created doesn't need further modification
-        
+
     def addGroupPQuota(self, gpq) :
         """Initializes a group print quota on a printer."""
@@ -1104,14 +1104,14 @@
                    "pykotaPrinterName" : pname,
                    "pykotaDateLimit" : "None",
-                 } 
+                 }
         if self.info["groupquotabase"].lower() == "group" :
             dn = "cn=%s,%s" % (uuid, gpq.Group.ident)
-        else :    
+        else :
             dn = "cn=%s,%s" % (uuid, self.info["groupquotabase"])
         self.doAdd(dn, fields)
         gpq.isDirty = False
         return None # the entry created doesn't need further modification
-        
-    def savePrinter(self, printer) :    
+
+    def savePrinter(self, printer) :
         """Saves the printer to the database in a single operation."""
         fields = {
@@ -1123,29 +1123,29 @@
                  }
         self.doModify(printer.ident, fields)
-        
+
     def saveUser(self, user) :
         """Saves the user to the database in a single operation."""
         newfields = {
                        "pykotaLimitBy" : unicodeToDatabase(user.LimitBy or u"quota"),
-                       "description" : unicodeToDatabase(user.Description or ""), 
+                       "description" : unicodeToDatabase(user.Description or ""),
                        self.info["usermail"] : user.Email or "",
-                    }   
+                    }
         self.doModify(user.ident, newfields)
-        
+
         newfields = { "pykotaBalance" : str(user.AccountBalance or 0.0),
-                      "pykotaLifeTimePaid" : str(user.LifeTimePaid or 0.0), 
+                      "pykotaLifeTimePaid" : str(user.LifeTimePaid or 0.0),
                       "pykotaOverCharge" : str(user.OverCharge),
                     }
         self.doModify(user.idbalance, newfields)
-        
+
     def saveGroup(self, group) :
         """Saves the group to the database in a single operation."""
         newfields = {
                        "pykotaLimitBy" : unicodeToDatabase(group.LimitBy or u"quota"),
-                       "description" : unicodeToDatabase(group.Description or ""), 
-                    }   
+                       "description" : unicodeToDatabase(group.Description or ""),
+                    }
         self.doModify(group.ident, newfields)
-        
-    def writeUserPQuotaDateLimit(self, userpquota, datelimit) :    
+
+    def writeUserPQuotaDateLimit(self, userpquota, datelimit) :
         """Sets the date limit permanently for a user print quota."""
         fields = {
@@ -1153,6 +1153,6 @@
                  }
         return self.doModify(userpquota.ident, fields)
-            
-    def writeGroupPQuotaDateLimit(self, grouppquota, datelimit) :    
+
+    def writeGroupPQuotaDateLimit(self, grouppquota, datelimit) :
         """Sets the date limit permanently for a group print quota."""
         fields = {
@@ -1160,6 +1160,6 @@
                  }
         return self.doModify(grouppquota.ident, fields)
-        
-    def increaseUserPQuotaPagesCounters(self, userpquota, nbpages) :    
+
+    def increaseUserPQuotaPagesCounters(self, userpquota, nbpages) :
         """Increase page counters for a user print quota."""
         fields = {
@@ -1167,13 +1167,13 @@
                    "pykotaLifePageCounter" : { "operator" : "+", "value" : nbpages, "convert" : int },
                  }
-        return self.doModify(userpquota.ident, fields)         
-        
-    def decreaseUserAccountBalance(self, user, amount) :    
+        return self.doModify(userpquota.ident, fields)
+
+    def decreaseUserAccountBalance(self, user, amount) :
         """Decreases user's account balance from an amount."""
         fields = {
                    "pykotaBalance" : { "operator" : "-", "value" : amount, "convert" : float },
                  }
-        return self.doModify(user.idbalance, fields, flushcache=1)         
-       
+        return self.doModify(user.idbalance, fields, flushcache=1)
+
     def writeNewPayment(self, user, amount, comment="") :
         """Adds a new payment to the payments history."""
@@ -1185,7 +1185,7 @@
                    "pykotaPayments" : payments,
                  }
-        return self.doModify(user.idbalance, fields)         
-        
-    def writeLastJobSize(self, lastjob, jobsize, jobprice) :        
+        return self.doModify(user.idbalance, fields)
+
+    def writeLastJobSize(self, lastjob, jobsize, jobprice) :
         """Sets the last job's size permanently."""
         fields = {
@@ -1193,6 +1193,6 @@
                    "pykotaJobPrice" : str(jobprice),
                  }
-        self.doModify(lastjob.ident, fields)         
-        
+        self.doModify(lastjob.ident, fields)
+
     def writeJobNew(self, printer, user, jobid, pagecounter, action, jobsize=None, jobprice=None, filename=None, title=None, copies=None, options=None, clienthost=None, jobsizebytes=None, jobmd5sum=None, jobpages=None, jobbilling=None, precomputedsize=None, precomputedprice=None) :
         """Adds a job in a printer's history."""
@@ -1202,8 +1202,8 @@
             uuid = self.genUUID()
             dn = "cn=%s,%s" % (uuid, self.info["jobbase"])
-        else :    
+        else :
             uuid = printer.LastJob.ident[3:].split(",")[0]
             dn = printer.LastJob.ident
-        if self.privacy :    
+        if self.privacy :
             # For legal reasons, we want to hide the title, filename and options
             title = filename = options = u"hidden"
@@ -1216,9 +1216,9 @@
                    "pykotaPrinterPageCounter" : str(pagecounter),
                    "pykotaAction" : unicodeToDatabase(action),
-                   "pykotaFileName" : ((filename is None) and "None") or unicodeToDatabase(filename), 
-                   "pykotaTitle" : ((title is None) and "None") or unicodeToDatabase(title), 
-                   "pykotaCopies" : str(copies), 
-                   "pykotaOptions" : ((options is None) and "None") or unicodeToDatabase(options), 
-                   "pykotaHostName" : str(clienthost), 
+                   "pykotaFileName" : ((filename is None) and "None") or unicodeToDatabase(filename),
+                   "pykotaTitle" : ((title is None) and "None") or unicodeToDatabase(title),
+                   "pykotaCopies" : str(copies),
+                   "pykotaOptions" : ((options is None) and "None") or unicodeToDatabase(options),
+                   "pykotaHostName" : str(clienthost),
                    "pykotaJobSizeBytes" : str(jobsizebytes),
                    "pykotaMD5Sum" : unicodeToDatabase(jobmd5sum),
@@ -1229,18 +1229,18 @@
                  }
         if (not self.disablehistory) or (not printer.LastJob.Exists) :
-            if jobsize is not None :         
+            if jobsize is not None :
                 fields.update({ "pykotaJobSize" : str(jobsize), "pykotaJobPrice" : str(jobprice) })
             self.doAdd(dn, fields)
-        else :    
+        else :
             # here we explicitly want to reset jobsize to 'None' if needed
             fields.update({ "pykotaJobSize" : str(jobsize), "pykotaJobPrice" : str(jobprice) })
             self.doModify(dn, fields)
-            
+
         if printer.LastJob.Exists :
             fields = {
                        "pykotaLastJobIdent" : uuid,
                      }
-            self.doModify(printer.LastJob.lastjobident, fields)         
-        else :    
+            self.doModify(printer.LastJob.lastjobident, fields)
+        else :
             lastjuuid = self.genUUID()
             lastjdn = "cn=%s,%s" % (lastjuuid, self.info["lastjobbase"])
@@ -1250,10 +1250,10 @@
                        "pykotaPrinterName" : pname,
                        "pykotaLastJobIdent" : uuid,
-                     }  
-            self.doAdd(lastjdn, fields)          
-            
+                     }
+            self.doAdd(lastjdn, fields)
+
     def saveUserPQuota(self, userpquota) :
         """Saves an user print quota entry."""
-        fields = { 
+        fields = {
                    "pykotaSoftLimit" : str(userpquota.SoftLimit),
                    "pykotaHardLimit" : str(userpquota.HardLimit),
@@ -1265,12 +1265,12 @@
                  }
         self.doModify(userpquota.ident, fields)
-        
+
     def writeUserPQuotaWarnCount(self, userpquota, warncount) :
         """Sets the warn counter value for a user quota."""
-        fields = { 
+        fields = {
                    "pykotaWarnCount" : str(warncount or 0),
                  }
         self.doModify(userpquota.ident, fields)
-        
+
     def increaseUserPQuotaWarnCount(self, userpquota) :
         """Increases the warn counter value for a user quota."""
@@ -1278,9 +1278,9 @@
                    "pykotaWarnCount" : { "operator" : "+", "value" : 1, "convert" : int },
                  }
-        return self.doModify(userpquota.ident, fields)         
-        
+        return self.doModify(userpquota.ident, fields)
+
     def saveGroupPQuota(self, grouppquota) :
         """Saves a group print quota entry."""
-        fields = { 
+        fields = {
                    "pykotaSoftLimit" : str(grouppquota.SoftLimit),
                    "pykotaHardLimit" : str(grouppquota.HardLimit),
@@ -1289,5 +1289,5 @@
                  }
         self.doModify(grouppquota.ident, fields)
-            
+
     def writePrinterToGroup(self, pgroup, printer) :
         """Puts a printer into a printer group."""
@@ -1296,19 +1296,19 @@
             fields = {
                        "uniqueMember" : pgroup.uniqueMember
-                     }  
-            self.doModify(pgroup.ident, fields)         
-            
+                     }
+            self.doModify(pgroup.ident, fields)
+
     def removePrinterFromGroup(self, pgroup, printer) :
         """Removes a printer from a printer group."""
         try :
             pgroup.uniqueMember.remove(printer.ident)
-        except ValueError :    
+        except ValueError :
             pass
-        else :    
+        else :
             fields = {
                        "uniqueMember" : pgroup.uniqueMember,
-                     }  
-            self.doModify(pgroup.ident, fields)         
-            
+                     }
+            self.doModify(pgroup.ident, fields)
+
     def retrieveHistory(self, user=None, printer=None, hostname=None, billingcode=None, jobid=None, limit=100, start=None, end=None) :
         """Retrieves all print jobs for user on printer (or all) between start and end date, limited to first 100 results."""
@@ -1325,28 +1325,28 @@
         if jobid is not None :
             where.append("(pykotaJobId=%s)" % jobid) # TODO : jobid is text, so unicodeToDatabase(jobid) but do all of them as well.
-        if where :    
+        if where :
             where = "(&%s)" % "".join([precond] + where)
-        else :    
+        else :
             where = precond
-        jobs = []    
-        result = self.doSearch(where, fields=[ "pykotaJobSizeBytes", 
-                                               "pykotaHostName", 
-                                               "pykotaUserName", 
-                                               "pykotaPrinterName", 
-                                               "pykotaJobId", 
-                                               "pykotaPrinterPageCounter", 
-                                               "pykotaAction", 
-                                               "pykotaJobSize", 
-                                               "pykotaJobPrice", 
-                                               "pykotaFileName", 
-                                               "pykotaTitle", 
-                                               "pykotaCopies", 
-                                               "pykotaOptions", 
-                                               "pykotaBillingCode", 
-                                               "pykotaPages", 
-                                               "pykotaMD5Sum", 
+        jobs = []
+        result = self.doSearch(where, fields=[ "pykotaJobSizeBytes",
+                                               "pykotaHostName",
+                                               "pykotaUserName",
+                                               "pykotaPrinterName",
+                                               "pykotaJobId",
+                                               "pykotaPrinterPageCounter",
+                                               "pykotaAction",
+                                               "pykotaJobSize",
+                                               "pykotaJobPrice",
+                                               "pykotaFileName",
+                                               "pykotaTitle",
+                                               "pykotaCopies",
+                                               "pykotaOptions",
+                                               "pykotaBillingCode",
+                                               "pykotaPages",
+                                               "pykotaMD5Sum",
                                                "pykotaPrecomputedJobSize",
                                                "pykotaPrecomputedJobPrice",
-                                               "createTimestamp" ], 
+                                               "createTimestamp" ],
                                       base=self.info["jobbase"])
         if result :
@@ -1358,15 +1358,15 @@
                 try :
                     job.JobSize = int(fields.get("pykotaJobSize", [0])[0])
-                except ValueError :    
+                except ValueError :
                     job.JobSize = None
-                try :    
+                try :
                     job.JobPrice = float(fields.get("pykotaJobPrice", [0.0])[0])
                 except ValueError :
                     job.JobPrice = None
                 job.JobAction = databaseToUnicode(fields.get("pykotaAction", [""])[0])
-                job.JobFileName = databaseToUnicode(fields.get("pykotaFileName", [""])[0]) 
-                job.JobTitle = databaseToUnicode(fields.get("pykotaTitle", [""])[0]) 
+                job.JobFileName = databaseToUnicode(fields.get("pykotaFileName", [""])[0])
+                job.JobTitle = databaseToUnicode(fields.get("pykotaTitle", [""])[0])
                 job.JobCopies = int(fields.get("pykotaCopies", [0])[0])
-                job.JobOptions = databaseToUnicode(fields.get("pykotaOptions", [""])[0]) 
+                job.JobOptions = databaseToUnicode(fields.get("pykotaOptions", [""])[0])
                 job.JobHostName = databaseToUnicode(fields.get("pykotaHostName", [""])[0])
                 job.JobSizeBytes = fields.get("pykotaJobSizeBytes", [0L])[0]
@@ -1376,7 +1376,7 @@
                 try :
                     job.PrecomputedJobSize = int(fields.get("pykotaPrecomputedJobSize", [0])[0])
-                except ValueError :    
+                except ValueError :
                     job.PrecomputedJobSize = None
-                try :    
+                try :
                     job.PrecomputedJobPrice = float(fields.get("pykotaPrecomputedJobPrice", [0.0])[0])
                 except ValueError :
@@ -1395,13 +1395,13 @@
                     job.Exists = True
                     jobs.append(job)
-            jobs.sort(lambda x, y : cmp(y.JobDate, x.JobDate))        
-            if limit :    
+            jobs.sort(lambda x, y : cmp(y.JobDate, x.JobDate))
+            if limit :
                 jobs = jobs[:int(limit)]
         return jobs
-        
-    def deleteUser(self, user) :    
+
+    def deleteUser(self, user) :
         """Completely deletes an user from the Quota Storage."""
         uname = unicodeToDatabase(user.Name)
-        todelete = []    
+        todelete = []
         result = self.doSearch("(&(objectClass=pykotaJob)(pykotaUserName=%s))" % uname, base=self.info["jobbase"])
         for (ident, fields) in result :
@@ -1417,5 +1417,5 @@
             # ensure the user print quota entry will be deleted
             todelete.append(ident)
-            
+
             # if last job of current printer was printed by the user
             # to delete, we also need to delete the printer's last job entry.
@@ -1423,9 +1423,9 @@
             if printer.LastJob.UserName == user.Name :
                 todelete.append(printer.LastJob.lastjobident)
-            
-        for ident in todelete :    
+
+        for ident in todelete :
             self.doDelete(ident)
-            
-        result = self.doSearch("objectClass=pykotaAccount", None, base=user.ident, scope=ldap.SCOPE_BASE)    
+
+        result = self.doSearch("objectClass=pykotaAccount", None, base=user.ident, scope=ldap.SCOPE_BASE)
         if result :
             fields = result[0][1]
@@ -1433,16 +1433,16 @@
                 if k.startswith("pykota") :
                     del fields[k]
-                elif k.lower() == "objectclass" :    
+                elif k.lower() == "objectclass" :
                     todelete = []
                     for i in range(len(fields[k])) :
-                        if fields[k][i].startswith("pykota") : 
+                        if fields[k][i].startswith("pykota") :
                             todelete.append(i)
-                    todelete.sort()        
+                    todelete.sort()
                     todelete.reverse()
                     for i in todelete :
                         del fields[k][i]
             if fields.get("objectClass") or fields.get("objectclass") :
-                self.doModify(user.ident, fields, ignoreold=0)        
-            else :    
+                self.doModify(user.ident, fields, ignoreold=0)
+            else :
                 self.doDelete(user.ident)
         result = self.doSearch("(&(objectClass=pykotaAccountBalance)(pykotaUserName=%s))" % \
@@ -1452,6 +1452,6 @@
         for (ident, fields) in result :
             self.doDelete(ident)
-        
-    def deleteGroup(self, group) :    
+
+    def deleteGroup(self, group) :
         """Completely deletes a group from the Quota Storage."""
         gname = unicodeToDatabase(group.Name)
@@ -1466,5 +1466,5 @@
         for (ident, fields) in result :
             self.doDelete(ident)
-        result = self.doSearch("objectClass=pykotaGroup", None, base=group.ident, scope=ldap.SCOPE_BASE)    
+        result = self.doSearch("objectClass=pykotaGroup", None, base=group.ident, scope=ldap.SCOPE_BASE)
         if result :
             fields = result[0][1]
@@ -1472,39 +1472,39 @@
                 if k.startswith("pykota") :
                     del fields[k]
-                elif k.lower() == "objectclass" :    
+                elif k.lower() == "objectclass" :
                     todelete = []
                     for i in range(len(fields[k])) :
-                        if fields[k][i].startswith("pykota") : 
+                        if fields[k][i].startswith("pykota") :
                             todelete.append(i)
-                    todelete.sort()        
+                    todelete.sort()
                     todelete.reverse()
                     for i in todelete :
                         del fields[k][i]
             if fields.get("objectClass") or fields.get("objectclass") :
-                self.doModify(group.ident, fields, ignoreold=0)        
-            else :    
+                self.doModify(group.ident, fields, ignoreold=0)
+            else :
                 self.doDelete(group.ident)
-                
+
     def deleteManyBillingCodes(self, billingcodes) :
         """Deletes many billing codes."""
         for bcode in billingcodes :
             bcode.delete()
-        
-    def deleteManyUsers(self, users) :        
+
+    def deleteManyUsers(self, users) :
         """Deletes many users."""
         for user in users :
             user.delete()
-            
-    def deleteManyGroups(self, groups) :        
+
+    def deleteManyGroups(self, groups) :
         """Deletes many groups."""
         for group in groups :
             group.delete()
-        
-    def deleteManyPrinters(self, printers) :        
+
+    def deleteManyPrinters(self, printers) :
         """Deletes many printers."""
         for printer in printers :
             printer.delete()
-        
-    def deleteManyUserPQuotas(self, printers, users) :        
+
+    def deleteManyUserPQuotas(self, printers, users) :
         """Deletes many user print quota entries."""
         # TODO : grab all with a single (possibly VERY huge) filter if possible (might depend on the LDAP server !)
@@ -1514,5 +1514,5 @@
                 if upq.Exists :
                     upq.delete()
-            
+
     def deleteManyGroupPQuotas(self, printers, groups) :
         """Deletes many group print quota entries."""
@@ -1523,6 +1523,6 @@
                 if gpq.Exists :
                     gpq.delete()
-                
-    def deleteUserPQuota(self, upquota) :    
+
+    def deleteUserPQuota(self, upquota) :
         """Completely deletes an user print quota entry from the database."""
         uname = unicodeToDatabase(upquota.User.Name)
@@ -1536,10 +1536,10 @@
             self.doDelete(upquota.Printer.LastJob.lastjobident)
         self.doDelete(upquota.ident)
-        
-    def deleteGroupPQuota(self, gpquota) :    
+
+    def deleteGroupPQuota(self, gpquota) :
         """Completely deletes a group print quota entry from the database."""
         self.doDelete(gpquota.ident)
-                
-    def deletePrinter(self, printer) :    
+
+    def deletePrinter(self, printer) :
         """Completely deletes a printer from the Quota Storage."""
         pname = unicodeToDatabase(printer.Name)
@@ -1564,28 +1564,28 @@
         for (ident, fields) in result :
             self.doDelete(ident)
-        for parent in self.getParentPrinters(printer) :  
+        for parent in self.getParentPrinters(printer) :
             try :
                 parent.uniqueMember.remove(printer.ident)
-            except ValueError :    
+            except ValueError :
                 pass
-            else :    
+            else :
                 fields = {
                            "uniqueMember" : parent.uniqueMember,
-                         }  
-                self.doModify(parent.ident, fields)         
-        self.doDelete(printer.ident)    
-        
+                         }
+                self.doModify(parent.ident, fields)
+        self.doDelete(printer.ident)
+
     def deleteBillingCode(self, code) :
         """Deletes a billing code from the Quota Storage (no entries are deleted from the history)"""
         self.doDelete(code.ident)
-        
-    def sortRecords(self, fields, records, default, ordering) :     
+
+    def sortRecords(self, fields, records, default, ordering) :
         """Sort records based on list of fields prefixed with '+' (ASC) or '-' (DESC)."""
         fieldindexes = {}
         for i in range(len(fields)) :
             fieldindexes[fields[i]] = i
-        if not ordering :    
+        if not ordering :
             ordering = default
-        orderby = []    
+        orderby = []
         for orderkey in ordering :
             # Create ordering hints, ignoring unknown fields
@@ -1598,10 +1598,10 @@
                 if index is not None :
                     orderby.append((+1, index))
-            else :    
+            else :
                 index = fieldindexes.get(orderkey)
                 if index is not None :
                     orderby.append((+1, index))
-                
-        def compare(x, y, orderby=orderby) :    
+
+        def compare(x, y, orderby=orderby) :
             """Compares two records."""
             i = 0
@@ -1612,11 +1612,11 @@
                 if not result :
                     i += 1
-                else :    
+                else :
                     return sign * result
-            return 0 # identical keys        
-            
+            return 0 # identical keys
+
         records.sort(compare)
         return records
-        
+
     def extractPrinters(self, extractonly={}, ordering=[]) :
         """Extracts all printer records."""
@@ -1629,9 +1629,9 @@
                 if entry.PassThrough in (1, "1", "t", "true", "T", "TRUE", "True") :
                     passthrough = "t"
-                else :    
+                else :
                     passthrough = "f"
                 result.append((entry.ident, entry.Name, entry.PricePerPage, entry.PricePerJob, entry.Description, entry.MaxJobSize, passthrough))
-            return [fields] + self.sortRecords(fields, result, ["+dn"], ordering) 
-        
+            return [fields] + self.sortRecords(fields, result, ["+dn"], ordering)
+
     def extractUsers(self, extractonly={}, ordering=[]) :
         """Extracts all user records."""
@@ -1644,5 +1644,5 @@
                 result.append((entry.ident, entry.Name, entry.AccountBalance, entry.LifeTimePaid, entry.LimitBy, entry.Email, entry.Description, entry.OverCharge))
             return [fields] + self.sortRecords(fields, result, ["+dn"], ordering)
-        
+
     def extractBillingcodes(self, extractonly={}, ordering=[]) :
         """Extracts all billing codes records."""
@@ -1655,5 +1655,5 @@
                 result.append((entry.ident, entry.BillingCode, entry.Balance, entry.PageCounter, entry.Description))
             return [fields] + self.sortRecords(fields, result, ["+dn"], ordering)
-        
+
     def extractGroups(self, extractonly={}, ordering=[]) :
         """Extracts all group records."""
@@ -1666,5 +1666,5 @@
                 result.append((entry.ident, entry.Name, entry.LimitBy, entry.AccountBalance, entry.LifeTimePaid, entry.Description))
             return [fields] + self.sortRecords(fields, result, ["+dn"], ordering)
-        
+
     def extractPayments(self, extractonly={}, ordering=[]) :
         """Extracts all payment records."""
@@ -1685,5 +1685,5 @@
                         result.append((entry.Name, amount, date, description))
             return [fields] + self.sortRecords(fields, result, ["+date"], ordering)
-        
+
     def extractUpquotas(self, extractonly={}, ordering=[]) :
         """Extracts all userpquota records."""
@@ -1698,5 +1698,5 @@
                     result.append((user.Name, entry.Name, userpquota.ident, user.ident, entry.ident, userpquota.LifePageCounter, userpquota.PageCounter, userpquota.SoftLimit, userpquota.HardLimit, userpquota.DateLimit))
             return [fields] + self.sortRecords(fields, result, ["+userdn"], ordering)
-        
+
     def extractGpquotas(self, extractonly={}, ordering=[]) :
         """Extracts all grouppquota records."""
@@ -1711,5 +1711,5 @@
                     result.append((group.Name, entry.Name, grouppquota.ident, group.ident, entry.ident, grouppquota.LifePageCounter, grouppquota.PageCounter, grouppquota.SoftLimit, grouppquota.HardLimit, grouppquota.DateLimit))
             return [fields] + self.sortRecords(fields, result, ["+groupdn"], ordering)
-        
+
     def extractUmembers(self, extractonly={}, ordering=[]) :
         """Extracts all user groups members."""
@@ -1725,5 +1725,5 @@
                         result.append((entry.Name, member.Name, entry.ident, member.ident))
             return [fields] + self.sortRecords(fields, result, ["+groupdn", "+userdn"], ordering)
-                
+
     def extractPmembers(self, extractonly={}, ordering=[]) :
         """Extracts all printer groups members."""
@@ -1739,5 +1739,5 @@
                         result.append((parent.Name, entry.Name, parent.ident, entry.ident))
             return [fields] + self.sortRecords(fields, result, ["+pgroupdn", "+printerdn"], ordering)
-        
+
     def extractHistory(self, extractonly={}, ordering=[]) :
         """Extracts all jobhistory records."""
@@ -1745,10 +1745,10 @@
         if uname :
             user = self.getUser(uname)
-        else :    
+        else :
             user = None
         pname = extractonly.get("printername")
         if pname :
             printer = self.getPrinter(pname)
-        else :    
+        else :
             printer = None
         startdate = extractonly.get("start")
@@ -1760,7 +1760,7 @@
             result = []
             for entry in entries :
-                result.append((entry.UserName, entry.PrinterName, entry.ident, entry.JobId, entry.PrinterPageCounter, entry.JobSize, entry.JobAction, entry.JobDate, entry.JobFileName, entry.JobTitle, entry.JobCopies, entry.JobOptions, entry.JobPrice, entry.JobHostName, entry.JobSizeBytes, entry.JobMD5Sum, entry.JobPages, entry.JobBillingCode, entry.PrecomputedJobSize, entry.PrecomputedJobPrice)) 
+                result.append((entry.UserName, entry.PrinterName, entry.ident, entry.JobId, entry.PrinterPageCounter, entry.JobSize, entry.JobAction, entry.JobDate, entry.JobFileName, entry.JobTitle, entry.JobCopies, entry.JobOptions, entry.JobPrice, entry.JobHostName, entry.JobSizeBytes, entry.JobMD5Sum, entry.JobPages, entry.JobBillingCode, entry.PrecomputedJobSize, entry.PrecomputedJobPrice))
             return [fields] + self.sortRecords(fields, result, ["+dn"], ordering)
-            
+
     def getBillingCodeFromBackend(self, label) :
         """Extracts billing code information given its label : returns first matching billing code."""
@@ -1777,8 +1777,8 @@
             code.PageCounter = int(fields.get("pykotaPageCounter", [0])[0])
             code.Balance = float(fields.get("pykotaBalance", [0.0])[0])
-            code.Description = databaseToUnicode(fields.get("description", [""])[0]) 
+            code.Description = databaseToUnicode(fields.get("description", [""])[0])
             code.Exists = True
-        return code    
-        
+        return code
+
     def addBillingCode(self, bcode) :
         """Adds a billing code to the quota storage, returns it."""
@@ -1793,19 +1793,19 @@
                    "pykotaPageCounter" : str(bcode.PageCounter or 0),
                    "pykotaBalance" : str(bcode.Balance or 0.0),
-                   "description" : unicodeToDatabase(bcode.Description or ""), 
-                 } 
+                   "description" : unicodeToDatabase(bcode.Description or ""),
+                 }
         self.doAdd(dn, fields)
         bcode.isDirty = False
         return None # the entry created doesn't need further modification
-        
+
     def saveBillingCode(self, bcode) :
         """Sets the new description for a billing code."""
         fields = {
-                   "description" : unicodeToDatabase(bcode.Description or ""), 
+                   "description" : unicodeToDatabase(bcode.Description or ""),
                    "pykotaPageCounter" : str(bcode.PageCounter or 0),
                    "pykotaBalance" : str(bcode.Balance or 0.0),
                  }
         self.doModify(bcode.ident, fields)
-            
+
     def getMatchingBillingCodes(self, billingcodepattern) :
         """Returns the list of all billing codes which match a certain pattern."""
@@ -1818,5 +1818,5 @@
             try :
                 patdict = {}.fromkeys(patterns)
-            except AttributeError :    
+            except AttributeError :
                 # Python v2.2 or earlier
                 patdict = {}
@@ -1830,10 +1830,10 @@
                     code.PageCounter = int(fields.get("pykotaPageCounter", [0])[0])
                     code.Balance = float(fields.get("pykotaBalance", [0.0])[0])
-                    code.Description = databaseToUnicode(fields.get("description", [""])[0]) 
+                    code.Description = databaseToUnicode(fields.get("description", [""])[0])
                     code.Exists = True
                     codes.append(code)
                     self.cacheEntry("BILLINGCODES", code.BillingCode, code)
-        return codes        
-        
+        return codes
+
     def consumeBillingCode(self, bcode, pagecounter, balance) :
         """Consumes from a billing code."""
@@ -1842,13 +1842,13 @@
                    "pykotaPageCounter" : { "operator" : "+", "value" : pagecounter, "convert" : int },
                  }
-        return self.doModify(bcode.ident, fields)         
-
-    def refundJob(self, jobident) :   
+        return self.doModify(bcode.ident, fields)
+
+    def refundJob(self, jobident) :
         """Marks a job as refunded in the history."""
         fields = {
                      "pykotaAction" : "REFUND",
-                 }    
-        self.doModify(jobident, fields)         
-        
+                 }
+        self.doModify(jobident, fields)
+
     def storageUserFromRecord(self, username, record) :
         """Returns a StorageUser instance from a database record."""
@@ -1856,5 +1856,5 @@
         user.Exists = True
         return user
-        
+
     def storageGroupFromRecord(self, groupname, record) :
         """Returns a StorageGroup instance from a database record."""
@@ -1862,5 +1862,5 @@
         group.Exists = True
         return group
-        
+
     def storagePrinterFromRecord(self, printername, record) :
         """Returns a StoragePrinter instance from a database record."""
@@ -1868,9 +1868,9 @@
         printer.Exists = True
         return printer
-        
-    def setJobAttributesFromRecord(self, job, record) :    
+
+    def setJobAttributesFromRecord(self, job, record) :
         """Sets the attributes of a job from a database record."""
         job.Exists = True
-        
+
     def storageJobFromRecord(self, record) :
         """Returns a StorageJob instance from a database record."""
@@ -1878,5 +1878,5 @@
         self.setJobAttributesFromRecord(job, record)
         return job
-        
+
     def storageLastJobFromRecord(self, printer, record) :
         """Returns a StorageLastJob instance from a database record."""
@@ -1884,5 +1884,5 @@
         self.setJobAttributesFromRecord(lastjob, record)
         return lastjob
-        
+
     def storageUserPQuotaFromRecord(self, user, printer, record) :
         """Returns a StorageUserPQuota instance from a database record."""
@@ -1890,5 +1890,5 @@
         userpquota.Exists = True
         return userpquota
-        
+
     def storageGroupPQuotaFromRecord(self, group, printer, record) :
         """Returns a StorageGroupPQuota instance from a database record."""
@@ -1896,5 +1896,5 @@
         grouppquota.Exists = True
         return grouppquota
-        
+
     def storageBillingCodeFromRecord(self, billingcode, record) :
         """Returns a StorageBillingCode instance from a database record."""
