Index: /pykota/branches/1.26_fixes/pykota/storages/sql.py
===================================================================
--- /pykota/branches/1.26_fixes/pykota/storages/sql.py (revision 3393)
+++ /pykota/branches/1.26_fixes/pykota/storages/sql.py (revision 3522)
@@ -14,5 +14,5 @@
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 # GNU General Public License for more details.
-# 
+#
 # You should have received a copy of the GNU General Public License
 # along with this program; if not, write to the Free Software
@@ -28,4 +28,6 @@
                            StorageJob, StorageLastJob, StorageUserPQuota, \
                            StorageGroupPQuota, StorageBillingCode
+
+MAXINNAMES = 500 # Maximum number of non-patterns names to use in a single IN statement
 
 class SQLStorage :
@@ -42,5 +44,5 @@
         user.Exists = True
         return user
-        
+
     def storageGroupFromRecord(self, groupname, record) :
         """Returns a StorageGroup instance from a database record."""
@@ -53,5 +55,5 @@
         group.Exists = True
         return group
-        
+
     def storagePrinterFromRecord(self, printername, record) :
         """Returns a StoragePrinter instance from a database record."""
@@ -69,6 +71,6 @@
         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.ident = record.get("id")
@@ -78,8 +80,8 @@
         job.JobPrice = record.get("jobprice")
         job.JobAction = record.get("action")
-        job.JobFileName = self.databaseToUserCharset(record.get("filename") or "") 
-        job.JobTitle = self.databaseToUserCharset(record.get("title") or "") 
+        job.JobFileName = self.databaseToUserCharset(record.get("filename") or "")
+        job.JobTitle = self.databaseToUserCharset(record.get("title") or "")
         job.JobCopies = record.get("copies")
-        job.JobOptions = self.databaseToUserCharset(record.get("options") or "") 
+        job.JobOptions = self.databaseToUserCharset(record.get("options") or "")
         job.JobDate = record.get("jobdate")
         job.JobHostName = record.get("hostname")
@@ -95,5 +97,5 @@
             (job.JobTitle, job.JobFileName, job.JobOptions) = (_("Hidden because of privacy concerns"),) * 3
         job.Exists = True
-        
+
     def storageJobFromRecord(self, record) :
         """Returns a StorageJob instance from a database record."""
@@ -101,5 +103,5 @@
         self.setJobAttributesFromRecord(job, record)
         return job
-        
+
     def storageLastJobFromRecord(self, printer, record) :
         """Returns a StorageLastJob instance from a database record."""
@@ -107,5 +109,5 @@
         self.setJobAttributesFromRecord(lastjob, record)
         return lastjob
-        
+
     def storageUserPQuotaFromRecord(self, user, printer, record) :
         """Returns a StorageUserPQuota instance from a database record."""
@@ -120,5 +122,5 @@
         userpquota.Exists = True
         return userpquota
-        
+
     def storageGroupPQuotaFromRecord(self, group, printer, record) :
         """Returns a StorageGroupPQuota instance from a database record."""
@@ -135,5 +137,5 @@
         grouppquota.Exists = True
         return grouppquota
-        
+
     def storageBillingCodeFromRecord(self, billingcode, record) :
         """Returns a StorageBillingCode instance from a database record."""
@@ -145,6 +147,6 @@
         code.Exists = True
         return code
-        
-    def createFilter(self, only) :    
+
+    def createFilter(self, only) :
         """Returns the appropriate SQL filter."""
         if only :
@@ -152,21 +154,21 @@
             for (k, v) in only.items() :
                 expressions.append("%s=%s" % (k, self.doQuote(self.userCharsetToDatabase(v))))
-            return " AND ".join(expressions)     
-        return ""        
-        
-    def createOrderBy(self, default, ordering) :    
+            return " AND ".join(expressions)
+        return ""
+
+    def createOrderBy(self, default, ordering) :
         """Creates a suitable ORDER BY statement based on a list of fieldnames prefixed with '+' (ASC) or '-' (DESC)."""
         statements = []
         if not ordering :
             ordering = default
-        for field in ordering :    
-            if field.startswith("-") :    
+        for field in ordering :
+            if field.startswith("-") :
                 statements.append("%s DESC" % field[1:])
             elif field.startswith("+") :
                 statements.append("%s ASC" % field[1:])
-            else :    
+            else :
                 statements.append("%s ASC" % field)
-        return ", ".join(statements)    
-        
+        return ", ".join(statements)
+
     def extractPrinters(self, extractonly={}, ordering=[]) :
         """Extracts all printer records."""
@@ -177,5 +179,5 @@
         result = self.doRawSearch("SELECT * FROM printers %(thefilter)s ORDER BY %(orderby)s" % locals())
         return self.prepareRawResult(result)
-        
+
     def extractUsers(self, extractonly={}, ordering=[]) :
         """Extracts all user records."""
@@ -186,5 +188,5 @@
         result = self.doRawSearch("SELECT * FROM users %(thefilter)s ORDER BY %(orderby)s" % locals())
         return self.prepareRawResult(result)
-        
+
     def extractBillingcodes(self, extractonly={}, ordering=[]) :
         """Extracts all billing codes records."""
@@ -195,5 +197,5 @@
         result = self.doRawSearch("SELECT * FROM billingcodes %(thefilter)s ORDER BY %(orderby)s" % locals())
         return self.prepareRawResult(result)
-        
+
     def extractGroups(self, extractonly={}, ordering=[]) :
         """Extracts all group records."""
@@ -204,5 +206,5 @@
         result = self.doRawSearch("SELECT groups.*,COALESCE(SUM(balance), 0) AS balance, COALESCE(SUM(lifetimepaid), 0) as lifetimepaid FROM groups LEFT OUTER JOIN users ON users.id IN (SELECT userid FROM groupsmembers WHERE groupid=groups.id) %(thefilter)s GROUP BY groups.id,groups.groupname,groups.limitby,groups.description ORDER BY %(orderby)s" % locals())
         return self.prepareRawResult(result)
-        
+
     def extractPayments(self, extractonly={}, ordering=[]) :
         """Extracts all payment records."""
@@ -212,5 +214,5 @@
             try :
                 del extractonly[limit]
-            except KeyError :    
+            except KeyError :
                 pass
         thefilter = self.createFilter(extractonly)
@@ -218,12 +220,12 @@
             thefilter = "AND %s" % thefilter
         (startdate, enddate) = self.cleanDates(startdate, enddate)
-        if startdate : 
+        if startdate :
             thefilter = "%s AND date>=%s" % (thefilter, self.doQuote(startdate))
-        if enddate : 
+        if enddate :
             thefilter = "%s AND date<=%s" % (thefilter, self.doQuote(enddate))
         orderby = self.createOrderBy(["+payments.id"], ordering)
         result = self.doRawSearch("SELECT username,payments.* FROM users,payments WHERE users.id=payments.userid %(thefilter)s ORDER BY %(orderby)s" % locals())
         return self.prepareRawResult(result)
-        
+
     def extractUpquotas(self, extractonly={}, ordering=[]) :
         """Extracts all userpquota records."""
@@ -234,5 +236,5 @@
         result = self.doRawSearch("SELECT users.username,printers.printername,userpquota.* FROM users,printers,userpquota WHERE users.id=userpquota.userid AND printers.id=userpquota.printerid %(thefilter)s ORDER BY %(orderby)s" % locals())
         return self.prepareRawResult(result)
-        
+
     def extractGpquotas(self, extractonly={}, ordering=[]) :
         """Extracts all grouppquota records."""
@@ -243,5 +245,5 @@
         result = self.doRawSearch("SELECT groups.groupname,printers.printername,grouppquota.*,coalesce(sum(pagecounter), 0) AS pagecounter,coalesce(sum(lifepagecounter), 0) AS lifepagecounter FROM groups,printers,grouppquota,userpquota WHERE groups.id=grouppquota.groupid AND printers.id=grouppquota.printerid AND userpquota.printerid=grouppquota.printerid AND userpquota.userid IN (SELECT userid FROM groupsmembers WHERE groupsmembers.groupid=grouppquota.groupid) %(thefilter)s GROUP BY grouppquota.id,grouppquota.groupid,grouppquota.printerid,grouppquota.softlimit,grouppquota.hardlimit,grouppquota.datelimit,grouppquota.maxjobsize,groups.groupname,printers.printername ORDER BY %(orderby)s" % locals())
         return self.prepareRawResult(result)
-        
+
     def extractUmembers(self, extractonly={}, ordering=[]) :
         """Extracts all user groups members."""
@@ -252,5 +254,5 @@
         result = self.doRawSearch("SELECT groups.groupname, users.username, groupsmembers.* FROM groups,users,groupsmembers WHERE users.id=groupsmembers.userid AND groups.id=groupsmembers.groupid %(thefilter)s ORDER BY %(orderby)s" % locals())
         return self.prepareRawResult(result)
-        
+
     def extractPmembers(self, extractonly={}, ordering=[]) :
         """Extracts all printer groups members."""
@@ -268,5 +270,5 @@
         result = self.doRawSearch("SELECT p1.printername as pgroupname, p2.printername as printername, printergroupsmembers.* FROM printers p1, printers p2, printergroupsmembers WHERE p1.id=printergroupsmembers.groupid AND p2.id=printergroupsmembers.printerid %(thefilter)s ORDER BY %(orderby)s" % locals())
         return self.prepareRawResult(result)
-        
+
     def extractHistory(self, extractonly={}, ordering=[]) :
         """Extracts all jobhistory records."""
@@ -276,5 +278,5 @@
             try :
                 del extractonly[limit]
-            except KeyError :    
+            except KeyError :
                 pass
         thefilter = self.createFilter(extractonly)
@@ -282,17 +284,17 @@
             thefilter = "AND %s" % thefilter
         (startdate, enddate) = self.cleanDates(startdate, enddate)
-        if startdate : 
+        if startdate :
             thefilter = "%s AND jobdate>=%s" % (thefilter, self.doQuote(startdate))
-        if enddate : 
+        if enddate :
             thefilter = "%s AND jobdate<=%s" % (thefilter, self.doQuote(enddate))
         orderby = self.createOrderBy(["+jobhistory.id"], ordering)
         result = self.doRawSearch("SELECT users.username,printers.printername,jobhistory.* FROM users,printers,jobhistory WHERE users.id=jobhistory.userid AND printers.id=jobhistory.printerid %(thefilter)s ORDER BY %(orderby)s" % locals())
         return self.prepareRawResult(result)
-            
+
     def filterNames(self, records, attribute, patterns=None) :
         """Returns a list of 'attribute' from a list of records.
-        
+
            Logs any missing attribute.
-        """   
+        """
         result = []
         for record in records :
@@ -307,33 +309,33 @@
                     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."""
         result = self.doSearch("SELECT billingcode FROM billingcodes")
         if result :
             return self.filterNames(result, "billingcode", 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."""
         result = self.doSearch("SELECT printername FROM printers")
         if result :
             return self.filterNames(result, "printername", printername)
-        else :    
+        else :
             return []
-    
-    def getAllUsersNames(self, username=None) :    
+
+    def getAllUsersNames(self, username=None) :
         """Extracts all user names."""
         result = self.doSearch("SELECT username FROM users")
         if result :
             return self.filterNames(result, "username", username)
-        else :    
+        else :
             return []
-        
-    def getAllGroupsNames(self, groupname=None) :    
+
+    def getAllGroupsNames(self, groupname=None) :
         """Extracts all group names."""
         result = self.doSearch("SELECT groupname FROM groups")
@@ -342,5 +344,5 @@
         else :
             return []
-        
+
     def getUserNbJobsFromHistory(self, user) :
         """Returns the number of jobs the user has in history."""
@@ -349,6 +351,6 @@
             return result[0]["count"]
         return 0
-        
-    def getUserFromBackend(self, username) :    
+
+    def getUserFromBackend(self, username) :
         """Extracts user information given its name."""
         result = self.doSearch("SELECT * FROM users WHERE username=%s LIMIT 1"\
@@ -356,8 +358,8 @@
         if result :
             return self.storageUserFromRecord(username, result[0])
-        else :    
+        else :
             return StorageUser(self, username)
-       
-    def getGroupFromBackend(self, groupname) :    
+
+    def getGroupFromBackend(self, groupname) :
         """Extracts group information given its name."""
         result = self.doSearch("SELECT groups.*,COALESCE(SUM(balance), 0.0) AS balance, COALESCE(SUM(lifetimepaid), 0.0) AS lifetimepaid FROM groups LEFT OUTER JOIN users ON users.id IN (SELECT userid FROM groupsmembers WHERE groupid=groups.id) WHERE groupname=%s GROUP BY groups.id,groups.groupname,groups.limitby,groups.description LIMIT 1" \
@@ -365,8 +367,8 @@
         if result :
             return self.storageGroupFromRecord(groupname, result[0])
-        else :    
+        else :
             return StorageGroup(self, groupname)
-       
-    def getPrinterFromBackend(self, printername) :        
+
+    def getPrinterFromBackend(self, printername) :
         """Extracts printer information given its name."""
         result = self.doSearch("SELECT * FROM printers WHERE printername=%s LIMIT 1" \
@@ -374,8 +376,8 @@
         if result :
             return self.storagePrinterFromRecord(printername, result[0])
-        else :    
+        else :
             return StoragePrinter(self, printername)
-        
-    def getBillingCodeFromBackend(self, label) :        
+
+    def getBillingCodeFromBackend(self, label) :
         """Extracts a billing code information given its name."""
         result = self.doSearch("SELECT * FROM billingcodes WHERE billingcode=%s LIMIT 1" \
@@ -383,8 +385,8 @@
         if result :
             return self.storageBillingCodeFromRecord(label, result[0])
-        else :    
+        else :
             return StorageBillingCode(self, label)
-        
-    def getUserPQuotaFromBackend(self, user, printer) :        
+
+    def getUserPQuotaFromBackend(self, user, printer) :
         """Extracts a user print quota."""
         if printer.Exists and user.Exists :
@@ -394,6 +396,6 @@
                 return self.storageUserPQuotaFromRecord(user, printer, result[0])
         return StorageUserPQuota(self, user, printer)
-        
-    def getGroupPQuotaFromBackend(self, group, printer) :        
+
+    def getGroupPQuotaFromBackend(self, group, printer) :
         """Extracts a group print quota."""
         if printer.Exists and group.Exists :
@@ -403,14 +405,14 @@
                 return self.storageGroupPQuotaFromRecord(group, printer, result[0])
         return StorageGroupPQuota(self, group, printer)
-        
-    def getPrinterLastJobFromBackend(self, printer) :        
+
+    def getPrinterLastJobFromBackend(self, printer) :
         """Extracts a printer's last job information."""
         result = self.doSearch("SELECT jobhistory.id, jobid, userid, username, pagecounter, jobsize, jobprice, filename, title, copies, options, hostname, jobdate, md5sum, pages, billingcode, precomputedjobsize, precomputedjobprice FROM jobhistory, users WHERE printerid=%s AND userid=users.id ORDER BY jobdate DESC LIMIT 1" % self.doQuote(printer.ident))
         if result :
             return self.storageLastJobFromRecord(printer, result[0])
-        else :    
+        else :
             return StorageLastJob(self, printer)
-            
-    def getGroupMembersFromBackend(self, group) :        
+
+    def getGroupMembersFromBackend(self, group) :
         """Returns the group's members list."""
         groupmembers = []
@@ -422,7 +424,7 @@
                 groupmembers.append(user)
                 self.cacheEntry("USERS", user.Name, user)
-        return groupmembers        
-        
-    def getUserGroupsFromBackend(self, user) :        
+        return groupmembers
+
+    def getUserGroupsFromBackend(self, user) :
         """Returns the user's groups list."""
         groups = []
@@ -431,7 +433,7 @@
             for record in result :
                 groups.append(self.getGroup(self.databaseToUserCharset(record.get("groupname"))))
-        return groups        
-        
-    def getParentPrintersFromBackend(self, printer) :    
+        return groups
+
+    def getParentPrintersFromBackend(self, printer) :
         """Get all the printer groups this printer is a member of."""
         pgroups = []
@@ -444,5 +446,13 @@
                         pgroups.append(parentprinter)
         return pgroups
-        
+
+    def hasWildCards(self, pattern) :
+        """Returns True if the pattern contains wildcards, else False."""
+        specialchars = "*?[!" # no need to check for ] since [ would be there first
+        for specialchar in specialchars :
+            if specialchar in pattern :
+                return True
+        return False
+
     def getMatchingPrinters(self, printerpattern) :
         """Returns the list of all printers for which name matches a certain pattern."""
@@ -454,11 +464,5 @@
         if result :
             patterns = printerpattern.split(",")
-            try :
-                patdict = {}.fromkeys(patterns)
-            except AttributeError :    
-                # Python v2.2 or earlier
-                patdict = {}
-                for p in patterns :
-                    patdict[p] = None
+            patdict = {}.fromkeys(patterns)
             for record in result :
                 pname = self.databaseToUserCharset(record["printername"])
@@ -467,6 +471,6 @@
                     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."""
@@ -475,22 +479,41 @@
         # but we don't because other storages semantics may be different, so every
         # storage should use fnmatch to match patterns and be storage agnostic
-        result = self.doSearch("SELECT * FROM users")
-        if result :
-            patterns = userpattern.split(",")
-            try :
-                patdict = {}.fromkeys(patterns)
-            except AttributeError :    
-                # Python v2.2 or earlier
-                patdict = {}
-                for p in patterns :
-                    patdict[p] = None
-            for record in result :
-                uname = self.databaseToUserCharset(record["username"])
-                if patdict.has_key(uname) or self.tool.matchString(uname, patterns) :
-                    user = self.storageUserFromRecord(uname, record)
-                    users.append(user)
-                    self.cacheEntry("USERS", user.Name, user)
-        return users        
-        
+        #
+        # This doesn't prevent us from being smarter, thanks to bse@chalmers.se
+        userpattern = userpattern or "*"
+        patterns = userpattern.split(",")
+        patdict = {}.fromkeys(patterns)
+        patterns = patdict.keys() # Ensures the uniqueness of each pattern, but we lose the cmd line ordering
+        # BEWARE : if a single pattern contains wild cards, we'll still use the slow route.
+        if self.hasWildCards(userpattern) :
+            # Slow route
+            result = self.doSearch("SELECT * FROM users")
+            if result :
+                for record in result :
+                    uname = self.databaseToUserCharset(record["username"])
+                    if patdict.has_key(uname) or self.tool.matchString(uname, patterns) :
+                        user = self.storageUserFromRecord(uname, record)
+                        users.append(user)
+                        self.cacheEntry("USERS", user.Name, user)
+        else :
+            # Fast route (probably not faster with a few users)
+            while patterns :
+                subset = patterns[:MAXINNAMES]
+                nbpatterns = len(subset)
+                if nbpatterns == 1 :
+                    wherestmt = "username=%s" % self.doQuote(self.userCharsetToDatabase(subset[0]))
+                else :
+                    wherestmt = "username IN (%s)" % ",".join([self.doQuote(self.userCharsetToDatabase(p)) for p in subset])
+                result = self.doSearch("SELECT * FROM users WHERE %s" % wherestmt)
+                if result :
+                    for record in result :
+                        uname = self.databaseToUserCharset(record["username"])
+                        user = self.storageUserFromRecord(uname, record)
+                        users.append(user)
+                        self.cacheEntry("USERS", user.Name, user)
+                patterns = patterns[MAXINNAMES:]
+        users.sort(key=lambda u : u.Name) # Adds some ordering, we've already lost the cmd line one anyway.
+        return users
+
     def getMatchingGroups(self, grouppattern) :
         """Returns the list of all groups for which name matches a certain pattern."""
@@ -502,11 +525,5 @@
         if result :
             patterns = grouppattern.split(",")
-            try :
-                patdict = {}.fromkeys(patterns)
-            except AttributeError :    
-                # Python v2.2 or earlier
-                patdict = {}
-                for p in patterns :
-                    patdict[p] = None
+            patdict = {}.fromkeys(patterns)
             for record in result :
                 gname = self.databaseToUserCharset(record["groupname"])
@@ -515,6 +532,6 @@
                     groups.append(group)
                     self.cacheEntry("GROUPS", group.Name, group)
-        return groups        
-        
+        return groups
+
     def getMatchingBillingCodes(self, billingcodepattern) :
         """Returns the list of all billing codes for which the label matches a certain pattern."""
@@ -523,11 +540,5 @@
         if result :
             patterns = billingcodepattern.split(",")
-            try :
-                patdict = {}.fromkeys(patterns)
-            except AttributeError :    
-                # Python v2.2 or earlier
-                patdict = {}
-                for p in patterns :
-                    patdict[p] = None
+            patdict = {}.fromkeys(patterns)
             for record in result :
                 codename = self.databaseToUserCharset(record["billingcode"])
@@ -536,7 +547,7 @@
                     codes.append(code)
                     self.cacheEntry("BILLINGCODES", code.BillingCode, code)
-        return codes        
-        
-    def getPrinterUsersAndQuotas(self, printer, names=["*"]) :        
+        return codes
+
+    def getPrinterUsersAndQuotas(self, printer, names=["*"]) :
         """Returns the list of users who uses a given printer, along with their quotas."""
         usersandquotas = []
@@ -552,6 +563,6 @@
                     self.cacheEntry("USERPQUOTAS", "%s@%s" % (user.Name, printer.Name), userpquota)
         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 = []
@@ -565,6 +576,6 @@
                     groupsandquotas.append((group, grouppquota))
         return groupsandquotas
-        
-    def addPrinter(self, printer) :        
+
+    def addPrinter(self, printer) :
         """Adds a printer to the quota storage, returns the old value if it already exists."""
         oldentry = self.getPrinter(printer.Name)
@@ -580,5 +591,5 @@
         printer.isDirty = False
         return None # the entry created doesn't need further modification
-        
+
     def addBillingCode(self, bcode) :
         """Adds a billing code to the quota storage, returns the old value if it already exists."""
@@ -587,5 +598,5 @@
             return oldentry
         self.doModify("INSERT INTO billingcodes (billingcode, balance, pagecounter, description) VALUES (%s, %s, %s, %s)" \
-                           % (self.doQuote(self.userCharsetToDatabase(bcode.BillingCode)), 
+                           % (self.doQuote(self.userCharsetToDatabase(bcode.BillingCode)),
                               self.doQuote(bcode.Balance or 0.0), \
                               self.doQuote(bcode.PageCounter or 0), \
@@ -593,6 +604,6 @@
         bcode.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)
@@ -613,6 +624,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)
@@ -626,19 +637,19 @@
         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."""
         result = self.doSearch("SELECT COUNT(*) AS mexists FROM groupsmembers WHERE groupid=%s AND userid=%s" % (self.doQuote(group.ident), self.doQuote(user.ident)))
         try :
             mexists = int(result[0].get("mexists"))
-        except (IndexError, TypeError) :    
+        except (IndexError, TypeError) :
             mexists = 0
-        if not mexists :    
+        if not mexists :
             self.doModify("INSERT INTO groupsmembers (groupid, userid) VALUES (%s, %s)" % (self.doQuote(group.ident), self.doQuote(user.ident)))
-            
-    def delUserFromGroup(self, user, group) :    
+
+    def delUserFromGroup(self, user, group) :
         """Removes an user from a group."""
         self.doModify("DELETE FROM groupsmembers WHERE groupid=%s AND userid=%s" % \
                        (self.doQuote(group.ident), self.doQuote(user.ident)))
-            
+
     def addUserPQuota(self, upq) :
         """Initializes a user print quota on a printer."""
@@ -658,5 +669,5 @@
         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."""
@@ -673,6 +684,6 @@
         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."""
         self.doModify("UPDATE printers SET passthrough=%s, maxjobsize=%s, description=%s, priceperpage=%s, priceperjob=%s WHERE id=%s" \
@@ -683,6 +694,6 @@
                                  self.doQuote(printer.PricePerJob or 0.0), \
                                  self.doQuote(printer.ident)))
-                                 
-    def saveUser(self, user) :        
+
+    def saveUser(self, user) :
         """Saves the user to the database in a single operation."""
         self.doModify("UPDATE users SET limitby=%s, balance=%s, lifetimepaid=%s, email=%s, overcharge=%s, description=%s WHERE id=%s" \
@@ -694,6 +705,6 @@
                                   self.doQuote(self.userCharsetToDatabase(user.Description)), \
                                   self.doQuote(user.ident)))
-                                  
-    def saveGroup(self, group) :        
+
+    def saveGroup(self, group) :
         """Saves the group to the database in a single operation."""
         self.doModify("UPDATE groups SET limitby=%s, description=%s WHERE id=%s" \
@@ -701,18 +712,18 @@
                                   self.doQuote(self.userCharsetToDatabase(group.Description)), \
                                   self.doQuote(group.ident)))
-        
-    def writeUserPQuotaDateLimit(self, userpquota, datelimit) :    
+
+    def writeUserPQuotaDateLimit(self, userpquota, datelimit) :
         """Sets the date limit permanently for a user print quota."""
         self.doModify("UPDATE userpquota SET datelimit=%s WHERE id=%s" % (self.doQuote(datelimit), self.doQuote(userpquota.ident)))
-            
-    def writeGroupPQuotaDateLimit(self, grouppquota, datelimit) :    
+
+    def writeGroupPQuotaDateLimit(self, grouppquota, datelimit) :
         """Sets the date limit permanently for a group print quota."""
         self.doModify("UPDATE grouppquota SET datelimit=%s WHERE id=%s" % (self.doQuote(datelimit), self.doQuote(grouppquota.ident)))
-        
-    def increaseUserPQuotaPagesCounters(self, userpquota, nbpages) :    
+
+    def increaseUserPQuotaPagesCounters(self, userpquota, nbpages) :
         """Increase page counters for a user print quota."""
         self.doModify("UPDATE userpquota SET pagecounter=pagecounter + %s,lifepagecounter=lifepagecounter + %s WHERE id=%s" % (self.doQuote(nbpages), self.doQuote(nbpages), self.doQuote(userpquota.ident)))
-       
-    def saveBillingCode(self, bcode) :    
+
+    def saveBillingCode(self, bcode) :
         """Saves the billing code to the database."""
         self.doModify("UPDATE billingcodes SET balance=%s, pagecounter=%s, description=%s WHERE id=%s" \
@@ -721,31 +732,31 @@
                                self.doQuote(self.userCharsetToDatabase(bcode.Description)), \
                                self.doQuote(bcode.ident)))
-       
+
     def consumeBillingCode(self, bcode, pagecounter, balance) :
         """Consumes from a billing code."""
         self.doModify("UPDATE billingcodes SET balance=balance + %s, pagecounter=pagecounter + %s WHERE id=%s" % (self.doQuote(balance), self.doQuote(pagecounter), self.doQuote(bcode.ident)))
-       
-    def refundJob(self, jobident) :   
+
+    def refundJob(self, jobident) :
         """Marks a job as refunded in the history."""
         self.doModify("UPDATE jobhistory SET action='REFUND' WHERE id=%s;" % self.doQuote(jobident))
-        
-    def decreaseUserAccountBalance(self, user, amount) :    
+
+    def decreaseUserAccountBalance(self, user, amount) :
         """Decreases user's account balance from an amount."""
         self.doModify("UPDATE users SET balance=balance - %s WHERE id=%s" % (self.doQuote(amount), self.doQuote(user.ident)))
-       
+
     def writeNewPayment(self, user, amount, comment="") :
         """Adds a new payment to the payments history."""
         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 :    
+        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) :        
+
+    def writeLastJobSize(self, lastjob, jobsize, jobprice) :
         """Sets the last job's size permanently."""
         self.doModify("UPDATE jobhistory SET jobsize=%s, jobprice=%s WHERE id=%s" % (self.doQuote(jobsize), self.doQuote(jobprice), self.doQuote(lastjob.ident)))
-        
+
     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."""
-        if self.privacy :    
+        if self.privacy :
             # For legal reasons, we want to hide the title, filename and options
             title = filename = options = "hidden"
@@ -757,10 +768,10 @@
             if jobsize is not None :
                 self.doModify("INSERT INTO jobhistory (userid, printerid, jobid, pagecounter, action, jobsize, jobprice, filename, title, copies, options, hostname, jobsizebytes, md5sum, pages, billingcode, precomputedjobsize, precomputedjobprice) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)" % (self.doQuote(user.ident), self.doQuote(printer.ident), self.doQuote(jobid), self.doQuote(pagecounter), self.doQuote(action), self.doQuote(jobsize), self.doQuote(jobprice), self.doQuote(filename), self.doQuote(title), self.doQuote(copies), self.doQuote(options), self.doQuote(clienthost), self.doQuote(jobsizebytes), self.doQuote(jobmd5sum), self.doQuote(jobpages), self.doQuote(jobbilling), self.doQuote(precomputedsize), self.doQuote(precomputedprice)))
-            else :    
+            else :
                 self.doModify("INSERT INTO jobhistory (userid, printerid, jobid, pagecounter, action, filename, title, copies, options, hostname, jobsizebytes, md5sum, pages, billingcode, precomputedjobsize, precomputedjobprice) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)" % (self.doQuote(user.ident), self.doQuote(printer.ident), self.doQuote(jobid), self.doQuote(pagecounter), self.doQuote(action), self.doQuote(filename), self.doQuote(title), self.doQuote(copies), self.doQuote(options), self.doQuote(clienthost), self.doQuote(jobsizebytes), self.doQuote(jobmd5sum), self.doQuote(jobpages), self.doQuote(jobbilling), self.doQuote(precomputedsize), self.doQuote(precomputedprice)))
-        else :        
+        else :
             # here we explicitly want to reset jobsize to NULL if needed
             self.doModify("UPDATE jobhistory SET userid=%s, jobid=%s, pagecounter=%s, action=%s, jobsize=%s, jobprice=%s, filename=%s, title=%s, copies=%s, options=%s, hostname=%s, jobsizebytes=%s, md5sum=%s, pages=%s, billingcode=%s, precomputedjobsize=%s, precomputedjobprice=%s, jobdate=now() WHERE id=%s" % (self.doQuote(user.ident), self.doQuote(jobid), self.doQuote(pagecounter), self.doQuote(action), self.doQuote(jobsize), self.doQuote(jobprice), self.doQuote(filename), self.doQuote(title), self.doQuote(copies), self.doQuote(options), self.doQuote(clienthost), self.doQuote(jobsizebytes), self.doQuote(jobmd5sum), self.doQuote(jobpages), self.doQuote(jobbilling), self.doQuote(precomputedsize), self.doQuote(precomputedprice), self.doQuote(printer.LastJob.ident)))
-            
+
     def saveUserPQuota(self, userpquota) :
         """Saves an user print quota entry."""
@@ -774,13 +785,13 @@
                                  self.doQuote(userpquota.MaxJobSize), \
                                  self.doQuote(userpquota.ident)))
-        
+
     def writeUserPQuotaWarnCount(self, userpquota, warncount) :
         """Sets the warn counter value for a user quota."""
         self.doModify("UPDATE userpquota SET warncount=%s WHERE id=%s" % (self.doQuote(warncount), self.doQuote(userpquota.ident)))
-        
+
     def increaseUserPQuotaWarnCount(self, userpquota) :
         """Increases the warn counter value for a user quota."""
         self.doModify("UPDATE userpquota SET warncount=warncount+1 WHERE id=%s" % self.doQuote(userpquota.ident))
-        
+
     def saveGroupPQuota(self, grouppquota) :
         """Saves a group print quota entry."""
@@ -798,11 +809,11 @@
             for record in result :
                 children.append(record.get("printerid")) # TODO : put this into the database integrity rules
-        if printer.ident not in children :        
+        if printer.ident not in children :
             self.doModify("INSERT INTO printergroupsmembers (groupid, printerid) VALUES (%s, %s)" % (self.doQuote(pgroup.ident), self.doQuote(printer.ident)))
-        
+
     def removePrinterFromGroup(self, pgroup, printer) :
         """Removes a printer from a printer group."""
         self.doModify("DELETE FROM printergroupsmembers WHERE groupid=%s AND printerid=%s" % (self.doQuote(pgroup.ident), self.doQuote(printer.ident)))
-        
+
     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."""
@@ -813,21 +824,21 @@
         if printer is not None : # printer.ident is None anyway if printer doesn't exist
             where.append("printerid=%s" % self.doQuote(printer.ident))
-        if hostname is not None :    
+        if hostname is not None :
             where.append("hostname=%s" % self.doQuote(hostname))
-        if billingcode is not None :    
+        if billingcode is not None :
             where.append("billingcode=%s" % self.doQuote(self.userCharsetToDatabase(billingcode)))
-        if jobid is not None :    
+        if jobid is not None :
             where.append("jobid=%s" % self.doQuote(jobid)) # TODO : jobid is text, so self.userCharsetToDatabase(jobid) but do all of them as well.
-        if start is not None :    
+        if start is not None :
             where.append("jobdate>=%s" % self.doQuote(start))
-        if end is not None :    
+        if end is not None :
             where.append("jobdate<=%s" % self.doQuote(end))
-        if where :    
+        if where :
             query += " AND %s" % " AND ".join(where)
         query += " ORDER BY jobhistory.id DESC"
         if limit :
             query += " LIMIT %s" % self.doQuote(int(limit))
-        jobs = []    
-        result = self.doSearch(query)    
+        jobs = []
+        result = self.doSearch(query)
         if result :
             for fields in result :
@@ -835,11 +846,11 @@
                 jobs.append(job)
         return jobs
-        
-    def deleteUser(self, user) :    
+
+    def deleteUser(self, user) :
         """Completely deletes an user from the database."""
         # TODO : What should we do if we delete the last person who used a given printer ?
         # TODO : we can't reassign the last job to the previous one, because next user would be
         # TODO : incorrectly charged (overcharged).
-        for q in [ 
+        for q in [
                     "DELETE FROM payments WHERE userid=%s" % self.doQuote(user.ident),
                     "DELETE FROM groupsmembers WHERE userid=%s" % self.doQuote(user.ident),
@@ -849,6 +860,6 @@
                   ] :
             self.doModify(q)
-            
-    def multipleQueriesInTransaction(self, queries) :        
+
+    def multipleQueriesInTransaction(self, queries) :
         """Does many modifications in a single transaction."""
         self.beginTransaction()
@@ -856,22 +867,22 @@
             for q in queries :
                 self.doModify(q)
-        except :    
+        except :
             self.rollbackTransaction()
             raise
-        else :    
+        else :
             self.commitTransaction()
-            
-    def deleteManyBillingCodes(self, billingcodes) :        
+
+    def deleteManyBillingCodes(self, billingcodes) :
         """Deletes many billing codes."""
         codeids = ", ".join(["%s" % self.doQuote(b.ident) for b in billingcodes])
         if codeids :
-            self.multipleQueriesInTransaction([ 
+            self.multipleQueriesInTransaction([
                     "DELETE FROM billingcodes WHERE id IN (%s)" % codeids,])
-            
-    def deleteManyUsers(self, users) :        
+
+    def deleteManyUsers(self, users) :
         """Deletes many users."""
         userids = ", ".join(["%s" % self.doQuote(u.ident) for u in users])
         if userids :
-            self.multipleQueriesInTransaction([ 
+            self.multipleQueriesInTransaction([
                     "DELETE FROM payments WHERE userid IN (%s)" % userids,
                     "DELETE FROM groupsmembers WHERE userid IN (%s)" % userids,
@@ -879,19 +890,19 @@
                     "DELETE FROM userpquota WHERE userid IN (%s)" % userids,
                     "DELETE FROM users WHERE id IN (%s)" % userids,])
-                    
-    def deleteManyGroups(self, groups) :        
+
+    def deleteManyGroups(self, groups) :
         """Deletes many groups."""
         groupids = ", ".join(["%s" % self.doQuote(g.ident) for g in groups])
         if groupids :
-            self.multipleQueriesInTransaction([ 
+            self.multipleQueriesInTransaction([
                     "DELETE FROM groupsmembers WHERE groupid IN (%s)" % groupids,
                     "DELETE FROM grouppquota WHERE groupid IN (%s)" % groupids,
                     "DELETE FROM groups WHERE id IN (%s)" % groupids,])
-        
+
     def deleteManyPrinters(self, printers) :
         """Deletes many printers."""
         printerids = ", ".join(["%s" % self.doQuote(p.ident) for p in printers])
         if printerids :
-            self.multipleQueriesInTransaction([ 
+            self.multipleQueriesInTransaction([
                     "DELETE FROM printergroupsmembers WHERE groupid IN (%s) OR printerid IN (%s)" % (printerids, printerids),
                     "DELETE FROM jobhistory WHERE printerid IN (%s)" % printerids,
@@ -899,16 +910,16 @@
                     "DELETE FROM userpquota WHERE printerid IN (%s)" % printerids,
                     "DELETE FROM printers WHERE id IN (%s)" % printerids,])
-        
-    def deleteManyUserPQuotas(self, printers, users) :        
+
+    def deleteManyUserPQuotas(self, printers, users) :
         """Deletes many user print quota entries."""
         printerids = ", ".join(["%s" % self.doQuote(p.ident) for p in printers])
         userids = ", ".join(["%s" % self.doQuote(u.ident) for u in users])
         if userids and printerids :
-            self.multipleQueriesInTransaction([ 
+            self.multipleQueriesInTransaction([
                     "DELETE FROM jobhistory WHERE userid IN (%s) AND printerid IN (%s)" \
                                  % (userids, printerids),
                     "DELETE FROM userpquota WHERE userid IN (%s) AND printerid IN (%s)" \
                                  % (userids, printerids),])
-            
+
     def deleteManyGroupPQuotas(self, printers, groups) :
         """Deletes many group print quota entries."""
@@ -916,11 +927,11 @@
         groupids = ", ".join(["%s" % self.doQuote(g.ident) for g in groups])
         if groupids and printerids :
-            self.multipleQueriesInTransaction([ 
+            self.multipleQueriesInTransaction([
                     "DELETE FROM grouppquota WHERE groupid IN (%s) AND printerid IN (%s)" \
                                  % (groupids, printerids),])
-        
-    def deleteUserPQuota(self, upquota) :    
+
+    def deleteUserPQuota(self, upquota) :
         """Completely deletes an user print quota entry from the database."""
-        for q in [ 
+        for q in [
                     "DELETE FROM jobhistory WHERE userid=%s AND printerid=%s" \
                                  % (self.doQuote(upquota.User.ident), self.doQuote(upquota.Printer.ident)),
@@ -928,13 +939,13 @@
                   ] :
             self.doModify(q)
-        
-    def deleteGroupPQuota(self, gpquota) :    
+
+    def deleteGroupPQuota(self, gpquota) :
         """Completely deletes a group print quota entry from the database."""
-        for q in [ 
+        for q in [
                     "DELETE FROM grouppquota WHERE id=%s" % self.doQuote(gpquota.ident),
                   ] :
             self.doModify(q)
-        
-    def deleteGroup(self, group) :    
+
+    def deleteGroup(self, group) :
         """Completely deletes a group from the database."""
         for q in [
@@ -942,10 +953,10 @@
                    "DELETE FROM grouppquota WHERE groupid=%s" % self.doQuote(group.ident),
                    "DELETE FROM groups WHERE id=%s" % self.doQuote(group.ident),
-                 ] :  
+                 ] :
             self.doModify(q)
-            
-    def deletePrinter(self, printer) :    
+
+    def deletePrinter(self, printer) :
         """Completely deletes a printer from the database."""
-        for q in [ 
+        for q in [
                     "DELETE FROM printergroupsmembers WHERE groupid=%s OR printerid=%s" % (self.doQuote(printer.ident), self.doQuote(printer.ident)),
                     "DELETE FROM jobhistory WHERE printerid=%s" % self.doQuote(printer.ident),
@@ -955,10 +966,10 @@
                   ] :
             self.doModify(q)
-            
-    def deleteBillingCode(self, code) :    
+
+    def deleteBillingCode(self, code) :
         """Completely deletes a billing code from the database."""
         for q in [
                    "DELETE FROM billingcodes WHERE id=%s" % self.doQuote(code.ident),
-                 ] :  
+                 ] :
             self.doModify(q)
-        
+
Index: /pykota/branches/1.26_fixes/pykota/storages/ldapstorage.py
===================================================================
--- /pykota/branches/1.26_fixes/pykota/storages/ldapstorage.py (revision 3184)
+++ /pykota/branches/1.26_fixes/pykota/storages/ldapstorage.py (revision 3522)
@@ -14,5 +14,5 @@
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 # GNU General Public License for more details.
-# 
+#
 # You should have received a copy of the GNU General Public License
 # along with this program; if not, write to the Free Software
@@ -25,6 +25,6 @@
 """This module defines a class to access to an LDAP database backend.
 
-My IANA assigned number, for 
-"Conseil Internet & Logiciels Libres, J�me Alet" 
+My IANA assigned number, for
+"Conseil Internet & Logiciels Libres, J�me Alet"
 is 16868. Use this as a base to extend the LDAP schema.
 """
@@ -47,15 +47,15 @@
     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) :
@@ -67,6 +67,6 @@
         self.savedpasswd = passwd
         self.secondStageInit()
-        
-    def secondStageInit(self) :    
+
+    def secondStageInit(self) :
         """Second stage initialisation."""
         BaseStorage.__init__(self, self.savedtool)
@@ -76,5 +76,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
@@ -84,15 +84,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 :
@@ -102,7 +102,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 :
@@ -110,13 +110,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() :
@@ -124,20 +124,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."""
@@ -150,10 +150,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]
@@ -163,7 +163,7 @@
                     self.tool.logdebug("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" % str(msg)
                 self.tool.printInfo("LDAP error : %s" % message, "error")
@@ -171,5 +171,5 @@
                 self.close()
                 self.secondStageInit()
-            else :     
+            else :
                 self.tool.logdebug("QUERY : Result : %s" % result)
                 result = [ (dn, cidict(attrs)) for (dn, attrs) in result ]
@@ -180,5 +180,5 @@
                 return result
         raise PyKotaStorageError, message
-            
+
     def doAdd(self, dn, fields) :
         """Adds an entry in the LDAP directory."""
@@ -191,5 +191,5 @@
                 self.tool.logdebug("%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, str(msg))
             except ldap.LDAPError, msg :
@@ -205,5 +205,5 @@
                 return dn
         raise PyKotaStorageError, message
-            
+
     def doDelete(self, dn) :
         """Deletes an entry from the LDAP directory."""
@@ -213,5 +213,5 @@
                 self.tool.logdebug("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 :
@@ -221,14 +221,14 @@
                 self.close()
                 self.secondStageInit()
-            else :    
+            else :
                 if self.useldapcache :
                     try :
                         self.tool.logdebug("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."""
@@ -245,8 +245,8 @@
                             if k != "createTimestamp" :
                                 oldentry[k] = v
-                    else :    
+                    else :
                         self.tool.logdebug("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() :
@@ -254,10 +254,10 @@
                         try :
                             oldvalue = v["convert"](oldentry.get(k, [0])[0])
-                        except ValueError :    
+                        except ValueError :
                             self.tool.logdebug("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)
@@ -287,15 +287,15 @@
                             try :
                                 del cachedentry[mtyp]
-                            except KeyError :    
+                            except KeyError :
                                 pass
                     self.tool.logdebug("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 :
@@ -308,11 +308,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"
@@ -320,8 +320,8 @@
         if result :
             return [self.databaseToUserCharset(bc) for bc in 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"
@@ -329,8 +329,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"
@@ -338,8 +338,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"
@@ -347,13 +347,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))" % self.userCharsetToDatabase(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)
@@ -376,7 +376,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])
@@ -384,7 +384,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", []) :
@@ -395,11 +395,11 @@
                         (date, amount) = payment.split(" # ")
                         description = ""
-                    else :    
+                    else :
                         description = self.databaseToUserCharset(base64.decodestring(description))
                     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)
@@ -409,5 +409,5 @@
             fields = result[0][1]
             group.ident = result[0][0]
-            group.Name = fields.get("pykotaGroupName", [self.databaseToUserCharset(groupname)])[0] 
+            group.Name = fields.get("pykotaGroupName", [self.databaseToUserCharset(groupname)])[0]
             group.Description = self.databaseToUserCharset(fields.get("description", [None])[0])
             group.LimitBy = fields.get("pykotaLimitBy", ["quota"])[0]
@@ -420,6 +420,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)
@@ -434,5 +434,5 @@
             fields = result[0][1]       # take only first matching printer, ignore the rest
             printer.ident = result[0][0]
-            printer.Name = fields.get("pykotaPrinterName", [self.databaseToUserCharset(printername)])[0] 
+            printer.Name = fields.get("pykotaPrinterName", [self.databaseToUserCharset(printername)])[0]
             printer.PricePerJob = float(fields.get("pykotaPricePerJob", [0.0])[0])
             printer.PricePerPage = float(fields.get("pykotaPricePerPage", [0.0])[0])
@@ -444,9 +444,9 @@
                 printer.PassThrough = 0
             printer.uniqueMember = fields.get("uniqueMember", [])
-            printer.Description = self.databaseToUserCharset(fields.get("description", [""])[0]) 
+            printer.Description = self.databaseToUserCharset(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)
@@ -454,5 +454,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))" % \
@@ -470,5 +470,5 @@
                     if userpquota.SoftLimit[0].upper() == "NONE" :
                         userpquota.SoftLimit = None
-                    else :    
+                    else :
                         userpquota.SoftLimit = int(userpquota.SoftLimit[0])
                 userpquota.HardLimit = fields.get("pykotaHardLimit")
@@ -476,11 +476,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")
@@ -488,10 +488,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)
@@ -499,5 +499,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))" % \
@@ -512,5 +512,5 @@
                     if grouppquota.SoftLimit[0].upper() == "NONE" :
                         grouppquota.SoftLimit = None
-                    else :    
+                    else :
                         grouppquota.SoftLimit = int(grouppquota.SoftLimit[0])
                 grouppquota.HardLimit = fields.get("pykotaHardLimit")
@@ -518,11 +518,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")
@@ -530,5 +530,5 @@
                     if grouppquota.MaxJobSize[0].upper() == "NONE" :
                         grouppquota.MaxJobSize = None
-                    else :    
+                    else :
                         grouppquota.MaxJobSize = int(grouppquota.MaxJobSize[0])
                 grouppquota.PageCounter = 0
@@ -545,11 +545,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)
@@ -564,26 +564,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]
@@ -594,15 +594,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 = fields.get("pykotaAction", [""])[0]
-                lastjob.JobFileName = self.databaseToUserCharset(fields.get("pykotaFileName", [""])[0]) 
-                lastjob.JobTitle = self.databaseToUserCharset(fields.get("pykotaTitle", [""])[0]) 
+                lastjob.JobFileName = self.databaseToUserCharset(fields.get("pykotaFileName", [""])[0])
+                lastjob.JobTitle = self.databaseToUserCharset(fields.get("pykotaTitle", [""])[0])
                 lastjob.JobCopies = int(fields.get("pykotaCopies", [0])[0])
-                lastjob.JobOptions = self.databaseToUserCharset(fields.get("pykotaOptions", [""])[0]) 
+                lastjob.JobOptions = self.databaseToUserCharset(fields.get("pykotaOptions", [""])[0])
                 lastjob.JobHostName = fields.get("pykotaHostName", [""])[0]
                 lastjob.JobSizeBytes = fields.get("pykotaJobSizeBytes", [0L])[0]
@@ -612,9 +612,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 == "hidden" :
@@ -625,6 +625,6 @@
                 lastjob.Exists = True
         return lastjob
-        
-    def getGroupMembersFromBackend(self, group) :        
+
+    def getGroupMembersFromBackend(self, group) :
         """Returns the group's members list."""
         groupmembers = []
@@ -637,7 +637,7 @@
             for username in result[0][1].get(self.info["groupmembers"], []) :
                 groupmembers.append(self.getUser(self.databaseToUserCharset(username)))
-        return groupmembers        
-        
-    def getUserGroupsFromBackend(self, user) :        
+        return groupmembers
+
+    def getUserGroupsFromBackend(self, user) :
         """Returns the user's groups list."""
         groups = []
@@ -657,5 +657,5 @@
                     if group.LimitBy is not None :
                         group.LimitBy = group.LimitBy[0]
-                    else :    
+                    else :
                         group.LimitBy = "quota"
                     group.AccountBalance = 0.0
@@ -668,7 +668,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 = []
@@ -684,5 +684,5 @@
                         pgroups.append(parentprinter)
         return pgroups
-        
+
     def getMatchingPrinters(self, printerpattern) :
         """Returns the list of all printers for which name matches a certain pattern."""
@@ -694,11 +694,5 @@
         if result :
             patterns = printerpattern.split(",")
-            try :
-                patdict = {}.fromkeys(patterns)
-            except AttributeError :    
-                # Python v2.2 or earlier
-                patdict = {}
-                for p in patterns :
-                    patdict[p] = None
+            patdict = {}.fromkeys(patterns)
             for (printerid, fields) in result :
                 printername = self.databaseToUserCharset(fields.get("pykotaPrinterName", [""])[0] or fields.get(self.info["printerrdn"], [""])[0])
@@ -715,10 +709,10 @@
                         printer.PassThrough = 0
                     printer.uniqueMember = fields.get("uniqueMember", [])
-                    printer.Description = self.databaseToUserCharset(fields.get("description", [""])[0]) 
+                    printer.Description = self.databaseToUserCharset(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."""
@@ -730,11 +724,5 @@
         if result :
             patterns = userpattern.split(",")
-            try :
-                patdict = {}.fromkeys(patterns)
-            except AttributeError :    
-                # Python v2.2 or earlier
-                patdict = {}
-                for p in patterns :
-                    patdict[p] = None
+            patdict = {}.fromkeys(patterns)
             for (userid, fields) in result :
                 username = self.databaseToUserCharset(fields.get("pykotaUserName", [""])[0] or fields.get(self.info["userrdn"], [""])[0])
@@ -744,5 +732,5 @@
                     user.Email = fields.get(self.info["usermail"], [None])[0]
                     user.LimitBy = fields.get("pykotaLimitBy", ["quota"])[0]
-                    user.Description = self.databaseToUserCharset(fields.get("description", [""])[0]) 
+                    user.Description = self.databaseToUserCharset(fields.get("description", [""])[0])
                     uname = self.userCharsetToDatabase(username)
                     result = self.doSearch("(&(objectClass=pykotaAccountBalance)(|(pykotaUserName=%s)(%s=%s)))" % \
@@ -760,14 +748,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", []) :
@@ -778,5 +766,5 @@
                                 (date, amount) = payment.split(" # ")
                                 description = ""
-                            else :    
+                            else :
                                 description = self.databaseToUserCharset(base64.decodestring(description))
                             user.Payments.append((date, float(amount), description))
@@ -784,6 +772,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."""
@@ -795,11 +783,5 @@
         if result :
             patterns = grouppattern.split(",")
-            try :
-                patdict = {}.fromkeys(patterns)
-            except AttributeError :    
-                # Python v2.2 or earlier
-                patdict = {}
-                for p in patterns :
-                    patdict[p] = None
+            patdict = {}.fromkeys(patterns)
             for (groupid, fields) in result :
                 groupname = self.databaseToUserCharset(fields.get("pykotaGroupName", [""])[0] or fields.get(self.info["grouprdn"], [""])[0])
@@ -807,7 +789,7 @@
                     group = StorageGroup(self, groupname)
                     group.ident = groupid
-                    group.Name = fields.get("pykotaGroupName", [self.databaseToUserCharset(groupname)])[0] 
+                    group.Name = fields.get("pykotaGroupName", [self.databaseToUserCharset(groupname)])[0]
                     group.LimitBy = fields.get("pykotaLimitBy", ["quota"])[0]
-                    group.Description = self.databaseToUserCharset(fields.get("description", [""])[0]) 
+                    group.Description = self.databaseToUserCharset(fields.get("description", [""])[0])
                     group.AccountBalance = 0.0
                     group.LifeTimePaid = 0.0
@@ -820,6 +802,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 = []
@@ -846,5 +828,5 @@
                     if userpquota.SoftLimit[0].upper() == "NONE" :
                         userpquota.SoftLimit = None
-                    else :    
+                    else :
                         userpquota.SoftLimit = int(userpquota.SoftLimit[0])
                 userpquota.HardLimit = fields.get("pykotaHardLimit")
@@ -852,19 +834,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 = []
@@ -884,7 +866,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."""
@@ -902,11 +884,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)
@@ -919,6 +901,6 @@
                        "description" : self.userCharsetToDatabase(user.Description or ""),
                        self.info["usermail"] : user.Email or "",
-                    }   
-                       
+                    }
+
         mustadd = 1
         if self.info["newuser"].lower() != 'below' :
@@ -938,16 +920,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"],
@@ -955,15 +937,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"],
@@ -971,6 +953,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)
@@ -982,6 +964,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)
@@ -989,9 +971,9 @@
             return oldentry # we return the existing entry
         gname = self.userCharsetToDatabase(group.Name)
-        newfields = { 
+        newfields = {
                       "pykotaGroupName" : gname,
                       "pykotaLimitBy" : (group.LimitBy or "quota"),
                       "description" : self.userCharsetToDatabase(group.Description or "")
-                    } 
+                    }
         mustadd = 1
         if self.info["newgroup"].lower() != 'below' :
@@ -1013,24 +995,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]
@@ -1040,6 +1022,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)] :
@@ -1049,5 +1031,5 @@
                 if not fields.has_key(self.info["groupmembers"]) :
                     fields[self.info["groupmembers"]] = []
-                try :    
+                try :
                     fields[self.info["groupmembers"]].remove(self.userCharsetToDatabase(user.Name))
                 except ValueError :
@@ -1056,5 +1038,5 @@
                     self.doModify(group.ident, fields)
                     group.Members.remove(user)
-                
+
     def addUserPQuota(self, upq) :
         """Initializes a user print quota on a printer."""
@@ -1077,13 +1059,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."""
@@ -1099,14 +1081,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 = {
@@ -1118,29 +1100,29 @@
                  }
         self.doModify(printer.ident, fields)
-        
+
     def saveUser(self, user) :
         """Saves the user to the database in a single operation."""
         newfields = {
                        "pykotaLimitBy" : (user.LimitBy or "quota"),
-                       "description" : self.userCharsetToDatabase(user.Description or ""), 
+                       "description" : self.userCharsetToDatabase(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" : (group.LimitBy or "quota"),
-                       "description" : self.userCharsetToDatabase(group.Description or ""), 
-                    }   
+                       "description" : self.userCharsetToDatabase(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 = {
@@ -1148,6 +1130,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 = {
@@ -1155,6 +1137,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 = {
@@ -1162,13 +1144,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."""
@@ -1180,7 +1162,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 = {
@@ -1188,6 +1170,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."""
@@ -1197,8 +1179,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 = "hidden"
@@ -1211,9 +1193,9 @@
                    "pykotaPrinterPageCounter" : str(pagecounter),
                    "pykotaAction" : action,
-                   "pykotaFileName" : ((filename is None) and "None") or self.userCharsetToDatabase(filename), 
-                   "pykotaTitle" : ((title is None) and "None") or self.userCharsetToDatabase(title), 
-                   "pykotaCopies" : str(copies), 
-                   "pykotaOptions" : ((options is None) and "None") or self.userCharsetToDatabase(options), 
-                   "pykotaHostName" : str(clienthost), 
+                   "pykotaFileName" : ((filename is None) and "None") or self.userCharsetToDatabase(filename),
+                   "pykotaTitle" : ((title is None) and "None") or self.userCharsetToDatabase(title),
+                   "pykotaCopies" : str(copies),
+                   "pykotaOptions" : ((options is None) and "None") or self.userCharsetToDatabase(options),
+                   "pykotaHostName" : str(clienthost),
                    "pykotaJobSizeBytes" : str(jobsizebytes),
                    "pykotaMD5Sum" : str(jobmd5sum),
@@ -1224,18 +1206,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"])
@@ -1245,10 +1227,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),
@@ -1260,12 +1242,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."""
@@ -1273,9 +1255,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),
@@ -1284,5 +1266,5 @@
                  }
         self.doModify(grouppquota.ident, fields)
-            
+
     def writePrinterToGroup(self, pgroup, printer) :
         """Puts a printer into a printer group."""
@@ -1291,19 +1273,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."""
@@ -1320,28 +1302,28 @@
         if jobid is not None :
             where.append("(pykotaJobId=%s)" % jobid) # TODO : jobid is text, so self.userCharsetToDatabase(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 :
@@ -1353,15 +1335,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 = fields.get("pykotaAction", [""])[0]
-                job.JobFileName = self.databaseToUserCharset(fields.get("pykotaFileName", [""])[0]) 
-                job.JobTitle = self.databaseToUserCharset(fields.get("pykotaTitle", [""])[0]) 
+                job.JobFileName = self.databaseToUserCharset(fields.get("pykotaFileName", [""])[0])
+                job.JobTitle = self.databaseToUserCharset(fields.get("pykotaTitle", [""])[0])
                 job.JobCopies = int(fields.get("pykotaCopies", [0])[0])
-                job.JobOptions = self.databaseToUserCharset(fields.get("pykotaOptions", [""])[0]) 
+                job.JobOptions = self.databaseToUserCharset(fields.get("pykotaOptions", [""])[0])
                 job.JobHostName = fields.get("pykotaHostName", [""])[0]
                 job.JobSizeBytes = fields.get("pykotaJobSizeBytes", [0L])[0]
@@ -1371,7 +1353,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 :
@@ -1390,13 +1372,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 = self.userCharsetToDatabase(user.Name)
-        todelete = []    
+        todelete = []
         result = self.doSearch("(&(objectClass=pykotaJob)(pykotaUserName=%s))" % uname, base=self.info["jobbase"])
         for (ident, fields) in result :
@@ -1412,5 +1394,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.
@@ -1418,9 +1400,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]
@@ -1428,16 +1410,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))" % \
@@ -1447,6 +1429,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 = self.userCharsetToDatabase(group.Name)
@@ -1461,5 +1443,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]
@@ -1467,39 +1449,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 !)
@@ -1509,5 +1491,5 @@
                 if upq.Exists :
                     upq.delete()
-            
+
     def deleteManyGroupPQuotas(self, printers, groups) :
         """Deletes many group print quota entries."""
@@ -1518,6 +1500,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 = self.userCharsetToDatabase(upquota.User.Name)
@@ -1531,10 +1513,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 = self.userCharsetToDatabase(printer.Name)
@@ -1559,28 +1541,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
@@ -1593,10 +1575,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
@@ -1607,11 +1589,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."""
@@ -1624,9 +1606,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."""
@@ -1639,5 +1621,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."""
@@ -1650,5 +1632,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."""
@@ -1661,5 +1643,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."""
@@ -1680,5 +1662,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."""
@@ -1693,5 +1675,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."""
@@ -1706,5 +1688,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."""
@@ -1720,5 +1702,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."""
@@ -1734,5 +1716,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."""
@@ -1740,10 +1722,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")
@@ -1755,7 +1737,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."""
@@ -1772,8 +1754,8 @@
             code.PageCounter = int(fields.get("pykotaPageCounter", [0])[0])
             code.Balance = float(fields.get("pykotaBalance", [0.0])[0])
-            code.Description = self.databaseToUserCharset(fields.get("description", [""])[0]) 
+            code.Description = self.databaseToUserCharset(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."""
@@ -1788,19 +1770,19 @@
                    "pykotaPageCounter" : str(bcode.PageCounter or 0),
                    "pykotaBalance" : str(bcode.Balance or 0.0),
-                   "description" : self.userCharsetToDatabase(bcode.Description or ""), 
-                 } 
+                   "description" : self.userCharsetToDatabase(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" : self.userCharsetToDatabase(bcode.Description or ""), 
+                   "description" : self.userCharsetToDatabase(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."""
@@ -1811,11 +1793,5 @@
         if result :
             patterns = billingcodepattern.split(",")
-            try :
-                patdict = {}.fromkeys(patterns)
-            except AttributeError :    
-                # Python v2.2 or earlier
-                patdict = {}
-                for p in patterns :
-                    patdict[p] = None
+            patdict = {}.fromkeys(patterns)
             for (codeid, fields) in result :
                 codename = self.databaseToUserCharset(fields.get("pykotaBillingCode", [""])[0])
@@ -1825,10 +1801,10 @@
                     code.PageCounter = int(fields.get("pykotaPageCounter", [0])[0])
                     code.Balance = float(fields.get("pykotaBalance", [0.0])[0])
-                    code.Description = self.databaseToUserCharset(fields.get("description", [""])[0]) 
+                    code.Description = self.databaseToUserCharset(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."""
@@ -1837,14 +1813,14 @@
                    "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."""
         dn = "cn=%s,%s" % (ident, self.info["jobbase"])
         fields = {
                      "pykotaAction" : "REFUND",
-                 }    
-        self.doModify(dn, fields)         
-        
+                 }
+        self.doModify(dn, fields)
+
     def storageUserFromRecord(self, username, record) :
         """Returns a StorageUser instance from a database record."""
@@ -1852,5 +1828,5 @@
         user.Exists = True
         return user
-        
+
     def storageGroupFromRecord(self, groupname, record) :
         """Returns a StorageGroup instance from a database record."""
@@ -1858,5 +1834,5 @@
         group.Exists = True
         return group
-        
+
     def storagePrinterFromRecord(self, printername, record) :
         """Returns a StoragePrinter instance from a database record."""
@@ -1864,9 +1840,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."""
@@ -1874,5 +1850,5 @@
         self.setJobAttributesFromRecord(job, record)
         return job
-        
+
     def storageLastJobFromRecord(self, printer, record) :
         """Returns a StorageLastJob instance from a database record."""
@@ -1880,5 +1856,5 @@
         self.setJobAttributesFromRecord(lastjob, record)
         return lastjob
-        
+
     def storageUserPQuotaFromRecord(self, user, printer, record) :
         """Returns a StorageUserPQuota instance from a database record."""
@@ -1886,5 +1862,5 @@
         userpquota.Exists = True
         return userpquota
-        
+
     def storageGroupPQuotaFromRecord(self, group, printer, record) :
         """Returns a StorageGroupPQuota instance from a database record."""
@@ -1892,5 +1868,5 @@
         grouppquota.Exists = True
         return grouppquota
-        
+
     def storageBillingCodeFromRecord(self, billingcode, record) :
         """Returns a StorageBillingCode instance from a database record."""
