Index: pykota/trunk/pykota/storages/sql.py
===================================================================
--- pykota/trunk/pykota/storages/sql.py (revision 3411)
+++ pykota/trunk/pykota/storages/sql.py (revision 3413)
@@ -8,10 +8,10 @@
 # the Free Software Foundation, either version 3 of the License, or
 # (at your option) any later version.
-# 
+#
 # This program is distributed in the hope that it will be useful,
 # but WITHOUT ANY WARRANTY; without even the implied warranty of
 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 # GNU General Public License for more details.
-# 
+#
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
@@ -26,6 +26,6 @@
                            StorageJob, StorageLastJob, StorageUserPQuota, \
                            StorageGroupPQuota, StorageBillingCode
-                           
-from pykota.utils import *                           
+
+from pykota.utils import *
 
 class SQLStorage :
@@ -42,5 +42,5 @@
         user.Exists = True
         return user
-        
+
     def storageGroupFromRecord(self, groupname, record) :
         """Returns a StorageGroup instance from a database record."""
@@ -53,5 +53,5 @@
         group.Exists = True
         return group
-        
+
     def storagePrinterFromRecord(self, printername, record) :
         """Returns a StoragePrinter instance from a database record."""
@@ -69,6 +69,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 +78,8 @@
         job.JobPrice = record.get("jobprice")
         job.JobAction = record.get("action")
-        job.JobFileName = databaseToUnicode(record.get("filename") or "") 
-        job.JobTitle = databaseToUnicode(record.get("title") or "") 
+        job.JobFileName = databaseToUnicode(record.get("filename") or "")
+        job.JobTitle = databaseToUnicode(record.get("title") or "")
         job.JobCopies = record.get("copies")
-        job.JobOptions = databaseToUnicode(record.get("options") or "") 
+        job.JobOptions = databaseToUnicode(record.get("options") or "")
         job.JobDate = record.get("jobdate")
         job.JobHostName = record.get("hostname")
@@ -95,5 +95,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 +101,5 @@
         self.setJobAttributesFromRecord(job, record)
         return job
-        
+
     def storageLastJobFromRecord(self, printer, record) :
         """Returns a StorageLastJob instance from a database record."""
@@ -107,5 +107,5 @@
         self.setJobAttributesFromRecord(lastjob, record)
         return lastjob
-        
+
     def storageUserPQuotaFromRecord(self, user, printer, record) :
         """Returns a StorageUserPQuota instance from a database record."""
@@ -120,5 +120,5 @@
         userpquota.Exists = True
         return userpquota
-        
+
     def storageGroupPQuotaFromRecord(self, group, printer, record) :
         """Returns a StorageGroupPQuota instance from a database record."""
@@ -135,5 +135,5 @@
         grouppquota.Exists = True
         return grouppquota
-        
+
     def storageBillingCodeFromRecord(self, billingcode, record) :
         """Returns a StorageBillingCode instance from a database record."""
@@ -145,6 +145,6 @@
         code.Exists = True
         return code
-        
-    def createFilter(self, only) :    
+
+    def createFilter(self, only) :
         """Returns the appropriate SQL filter."""
         if only :
@@ -152,21 +152,21 @@
             for (k, v) in only.items() :
                 expressions.append("%s=%s" % (k, self.doQuote(unicodeToDatabase(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 +177,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 +186,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 +195,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 +204,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 +212,5 @@
             try :
                 del extractonly[limit]
-            except KeyError :    
+            except KeyError :
                 pass
         thefilter = self.createFilter(extractonly)
@@ -218,12 +218,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 +234,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 +243,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 +252,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 +268,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 +276,5 @@
             try :
                 del extractonly[limit]
-            except KeyError :    
+            except KeyError :
                 pass
         thefilter = self.createFilter(extractonly)
@@ -282,17 +282,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 +307,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 +342,5 @@
         else :
             return []
-        
+
     def getUserNbJobsFromHistory(self, user) :
         """Returns the number of jobs the user has in history."""
@@ -349,6 +349,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"\
@@ -356,8 +356,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" \
@@ -365,8 +365,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" \
@@ -374,8 +374,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" \
@@ -383,8 +383,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 +394,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 +403,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 userid=users.id AND jobhistory.id IN (SELECT max(id) FROM jobhistory WHERE printerid=%s)" % 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 +422,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 +431,7 @@
             for record in result :
                 groups.append(self.getGroup(databaseToUnicode(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 +444,5 @@
                         pgroups.append(parentprinter)
         return pgroups
-        
+
     def getMatchingPrinters(self, printerpattern) :
         """Returns the list of all printers for which name matches a certain pattern."""
@@ -456,5 +456,5 @@
             try :
                 patdict = {}.fromkeys(patterns)
-            except AttributeError :    
+            except AttributeError :
                 # Python v2.2 or earlier
                 patdict = {}
@@ -467,6 +467,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."""
@@ -480,5 +480,5 @@
             try :
                 patdict = {}.fromkeys(patterns)
-            except AttributeError :    
+            except AttributeError :
                 # Python v2.2 or earlier
                 patdict = {}
@@ -491,6 +491,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."""
@@ -504,5 +504,5 @@
             try :
                 patdict = {}.fromkeys(patterns)
-            except AttributeError :    
+            except AttributeError :
                 # Python v2.2 or earlier
                 patdict = {}
@@ -515,6 +515,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."""
@@ -525,5 +525,5 @@
             try :
                 patdict = {}.fromkeys(patterns)
-            except AttributeError :    
+            except AttributeError :
                 # Python v2.2 or earlier
                 patdict = {}
@@ -536,7 +536,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 +552,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 +565,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 +580,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 +587,5 @@
             return oldentry
         self.doModify("INSERT INTO billingcodes (billingcode, balance, pagecounter, description) VALUES (%s, %s, %s, %s)" \
-                           % (self.doQuote(unicodeToDatabase(bcode.BillingCode)), 
+                           % (self.doQuote(unicodeToDatabase(bcode.BillingCode)),
                               self.doQuote(bcode.Balance or 0.0), \
                               self.doQuote(bcode.PageCounter or 0), \
@@ -593,6 +593,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 +613,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 +626,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 +658,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 +673,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 +683,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 +694,6 @@
                                   self.doQuote(unicodeToDatabase(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 +701,18 @@
                                   self.doQuote(unicodeToDatabase(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 +721,31 @@
                                self.doQuote(unicodeToDatabase(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(unicodeToDatabase(comment))))
-        else :    
+        else :
             self.doModify("INSERT INTO payments (userid, amount, description) VALUES ((SELECT id FROM users WHERE username=%s), %s, %s)" % (self.doQuote(unicodeToDatabase(user.Name)), self.doQuote(amount), self.doQuote(unicodeToDatabase(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 +757,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 +774,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 +798,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,15 +813,15 @@
         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(unicodeToDatabase(billingcode)))
-        if jobid is not None :    
+        if jobid is not None :
             where.append("jobid=%s" % self.doQuote(jobid)) # TODO : jobid is text, so unicodeToDatabase(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"
@@ -829,7 +829,7 @@
             # TODO : LIMIT is not supported under DB2.
             # TODO : so we must use something like " FETCH FIRST %s ROWS ONLY" % self.doQuote(int(limit))
-            query += " LIMIT %s" % self.doQuote(int(limit)) 
-        jobs = []    
-        result = self.doSearch(query)    
+            query += " LIMIT %s" % self.doQuote(int(limit))
+        jobs = []
+        result = self.doSearch(query)
         if result :
             for fields in result :
@@ -837,11 +837,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),
@@ -851,6 +851,6 @@
                   ] :
             self.doModify(q)
-            
-    def multipleQueriesInTransaction(self, queries) :        
+
+    def multipleQueriesInTransaction(self, queries) :
         """Does many modifications in a single transaction."""
         self.beginTransaction()
@@ -858,22 +858,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,
@@ -881,19 +881,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,
@@ -901,16 +901,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."""
@@ -918,11 +918,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)),
@@ -930,13 +930,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 [
@@ -944,10 +944,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),
@@ -957,10 +957,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)
-        
+
