Changeset 3522 for pykota

Show
Ignore:
Timestamp:
04/15/10 01:27:45 (15 years ago)
Author:
jerome
Message:

Backport of the fix to #52.

Location:
pykota/branches/1.26_fixes/pykota/storages
Files:
2 modified

Legend:

Unmodified
Added
Removed
  • pykota/branches/1.26_fixes/pykota/storages/ldapstorage.py

    r3184 r3522  
    1414# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
    1515# GNU General Public License for more details. 
    16 #  
     16# 
    1717# You should have received a copy of the GNU General Public License 
    1818# along with this program; if not, write to the Free Software 
     
    2525"""This module defines a class to access to an LDAP database backend. 
    2626 
    27 My IANA assigned number, for  
    28 "Conseil Internet & Logiciels Libres, J�me Alet"  
     27My IANA assigned number, for 
     28"Conseil Internet & Logiciels Libres, J�me Alet" 
    2929is 16868. Use this as a base to extend the LDAP schema. 
    3030""" 
     
    4747    import ldap 
    4848    import ldap.modlist 
    49 except ImportError :     
     49except ImportError : 
    5050    raise PyKotaStorageError, "This python version (%s) doesn't seem to have the python-ldap module installed correctly." % sys.version.split()[0] 
    51 else :     
     51else : 
    5252    try : 
    5353        from ldap.cidict import cidict 
    54     except ImportError :     
     54    except ImportError : 
    5555        import UserDict 
    5656        sys.stderr.write("ERROR: PyKota requires a newer version of python-ldap. Workaround activated. Please upgrade python-ldap !\n") 
    5757        class cidict(UserDict.UserDict) : 
    5858            pass # Fake it all, and don't care for case insensitivity : users who need it will have to upgrade. 
    59      
     59 
    6060class Storage(BaseStorage) : 
    6161    def __init__(self, pykotatool, host, dbname, user, passwd) : 
     
    6767        self.savedpasswd = passwd 
    6868        self.secondStageInit() 
    69          
    70     def secondStageInit(self) :     
     69 
     70    def secondStageInit(self) : 
    7171        """Second stage initialisation.""" 
    7272        BaseStorage.__init__(self, self.savedtool) 
     
    7676            try : 
    7777                self.tool.logdebug("Trying to open database (host=%s, dbname=%s, user=%s)..." % (self.savedhost, self.saveddbname, self.saveduser)) 
    78                 self.database = ldap.initialize(self.savedhost)  
     78                self.database = ldap.initialize(self.savedhost) 
    7979                if self.info["ldaptls"] : 
    8080                    # we want TLS 
     
    8484                self.database.simple_bind_s(self.saveduser, self.savedpasswd) 
    8585                self.basedn = self.saveddbname 
    86             except ldap.SERVER_DOWN :     
     86            except ldap.SERVER_DOWN : 
    8787                message = "LDAP backend for PyKota seems to be down !" 
    8888                self.tool.printInfo("%s" % message, "error") 
    8989                self.tool.printInfo("Trying again in 2 seconds...", "warn") 
    9090                time.sleep(2) 
    91             except ldap.LDAPError :     
     91            except ldap.LDAPError : 
    9292                message = "Unable to connect to LDAP server %s as %s." % (self.savedhost, self.saveduser) 
    9393                self.tool.printInfo("%s" % message, "error") 
    9494                self.tool.printInfo("Trying again in 2 seconds...", "warn") 
    9595                time.sleep(2) 
    96             else :     
     96            else : 
    9797                self.useldapcache = self.tool.config.getLDAPCache() 
    9898                if self.useldapcache : 
     
    102102                self.tool.logdebug("Database opened (host=%s, dbname=%s, user=%s)" % (self.savedhost, self.saveddbname, self.saveduser)) 
    103103                return # All is fine here. 
    104         raise PyKotaStorageError, message          
    105              
    106     def close(self) :     
     104        raise PyKotaStorageError, message 
     105 
     106    def close(self) : 
    107107        """Closes the database connection.""" 
    108108        if not self.closed : 
     
    110110            self.closed = 1 
    111111            self.tool.logdebug("Database closed.") 
    112          
    113     def genUUID(self) :     
     112 
     113    def genUUID(self) : 
    114114        """Generates an unique identifier. 
    115          
     115 
    116116           TODO : this one is not unique accross several print servers, but should be sufficient for testing. 
    117117        """ 
    118118        return md5.md5("%s-%s" % (time.time(), random.random())).hexdigest() 
    119          
    120     def normalizeFields(self, fields) :     
     119 
     120    def normalizeFields(self, fields) : 
    121121        """Ensure all items are lists.""" 
    122122        for (k, v) in fields.items() : 
     
    124124                if not v : 
    125125                    del fields[k] 
    126                 else :     
     126                else : 
    127127                    fields[k] = [ v ] 
    128         return fields         
    129          
    130     def beginTransaction(self) :     
     128        return fields 
     129 
     130    def beginTransaction(self) : 
    131131        """Starts a transaction.""" 
    132132        self.tool.logdebug("Transaction begins... WARNING : No transactions in LDAP !") 
    133          
    134     def commitTransaction(self) :     
     133 
     134    def commitTransaction(self) : 
    135135        """Commits a transaction.""" 
    136136        self.tool.logdebug("Transaction committed. WARNING : No transactions in LDAP !") 
    137          
    138     def rollbackTransaction(self) :      
     137 
     138    def rollbackTransaction(self) : 
    139139        """Rollbacks a transaction.""" 
    140140        self.tool.logdebug("Transaction aborted. WARNING : No transaction in LDAP !") 
    141          
     141 
    142142    def doSearch(self, key, fields=None, base="", scope=ldap.SCOPE_SUBTREE, flushcache=0) : 
    143143        """Does an LDAP search query.""" 
     
    150150                    # retrieve ALL user defined attributes ("*") 
    151151                    # + the createTimestamp attribute, needed by job history 
    152                     #  
     152                    # 
    153153                    # This may not work with all LDAP servers 
    154                     # but works at least in OpenLDAP (2.1.25)  
     154                    # but works at least in OpenLDAP (2.1.25) 
    155155                    # and iPlanet Directory Server (5.1 SP3) 
    156                     fields = ["*", "createTimestamp"]          
    157                      
     156                    fields = ["*", "createTimestamp"] 
     157 
    158158                if self.useldapcache and (not flushcache) and (scope == ldap.SCOPE_BASE) and self.ldapcache.has_key(base) : 
    159159                    entry = self.ldapcache[base] 
     
    163163                    self.tool.logdebug("QUERY : Filter : %s, BaseDN : %s, Scope : %s, Attributes : %s" % (key, base, scope, fields)) 
    164164                    result = self.database.search_s(base, scope, key, fields) 
    165             except ldap.NO_SUCH_OBJECT, msg :         
     165            except ldap.NO_SUCH_OBJECT, msg : 
    166166                raise PyKotaStorageError, (_("Search base %s doesn't seem to exist. Probable misconfiguration. Please double check /etc/pykota/pykota.conf : %s") % (base, msg)) 
    167             except ldap.LDAPError, msg :     
     167            except ldap.LDAPError, msg : 
    168168                message = (_("Search for %s(%s) from %s(scope=%s) returned no answer.") % (key, fields, base, scope)) + " : %s" % str(msg) 
    169169                self.tool.printInfo("LDAP error : %s" % message, "error") 
     
    171171                self.close() 
    172172                self.secondStageInit() 
    173             else :      
     173            else : 
    174174                self.tool.logdebug("QUERY : Result : %s" % result) 
    175175                result = [ (dn, cidict(attrs)) for (dn, attrs) in result ] 
     
    180180                return result 
    181181        raise PyKotaStorageError, message 
    182              
     182 
    183183    def doAdd(self, dn, fields) : 
    184184        """Adds an entry in the LDAP directory.""" 
     
    191191                self.tool.logdebug("%s" % entry) 
    192192                self.database.add_s(dn, entry) 
    193             except ldap.ALREADY_EXISTS, msg :         
     193            except ldap.ALREADY_EXISTS, msg : 
    194194                raise PyKotaStorageError, "Entry %s already exists : %s" % (dn, str(msg)) 
    195195            except ldap.LDAPError, msg : 
     
    205205                return dn 
    206206        raise PyKotaStorageError, message 
    207              
     207 
    208208    def doDelete(self, dn) : 
    209209        """Deletes an entry from the LDAP directory.""" 
     
    213213                self.tool.logdebug("QUERY : Delete(%s)" % dn) 
    214214                self.database.delete_s(dn) 
    215             except ldap.NO_SUCH_OBJECT :     
     215            except ldap.NO_SUCH_OBJECT : 
    216216                self.tool.printInfo("Entry %s was already missing before we deleted it. This **MAY** be normal." % dn, "info") 
    217217            except ldap.LDAPError, msg : 
     
    221221                self.close() 
    222222                self.secondStageInit() 
    223             else :     
     223            else : 
    224224                if self.useldapcache : 
    225225                    try : 
    226226                        self.tool.logdebug("LDAP cache del %s" % dn) 
    227227                        del self.ldapcache[dn] 
    228                     except KeyError :     
     228                    except KeyError : 
    229229                        pass 
    230                 return         
     230                return 
    231231        raise PyKotaStorageError, message 
    232              
     232 
    233233    def doModify(self, dn, fields, ignoreold=1, flushcache=0) : 
    234234        """Modifies an entry in the LDAP directory.""" 
     
    245245                            if k != "createTimestamp" : 
    246246                                oldentry[k] = v 
    247                     else :     
     247                    else : 
    248248                        self.tool.logdebug("LDAP cache miss %s" % dn) 
    249249                        oldentry = self.doSearch("objectClass=*", base=dn, scope=ldap.SCOPE_BASE)[0][1] 
    250                 else :         
     250                else : 
    251251                    oldentry = self.doSearch("objectClass=*", base=dn, scope=ldap.SCOPE_BASE, flushcache=flushcache)[0][1] 
    252252                for (k, v) in fields.items() : 
     
    254254                        try : 
    255255                            oldvalue = v["convert"](oldentry.get(k, [0])[0]) 
    256                         except ValueError :     
     256                        except ValueError : 
    257257                            self.tool.logdebug("Error converting %s with %s(%s)" % (oldentry.get(k), k, v)) 
    258258                            oldvalue = 0 
    259259                        if v["operator"] == '+' : 
    260260                            newvalue = oldvalue + v["value"] 
    261                         else :     
     261                        else : 
    262262                            newvalue = oldvalue - v["value"] 
    263263                        fields[k] = str(newvalue) 
     
    287287                            try : 
    288288                                del cachedentry[mtyp] 
    289                             except KeyError :     
     289                            except KeyError : 
    290290                                pass 
    291291                    self.tool.logdebug("LDAP cache update %s => %s" % (dn, cachedentry)) 
    292292                return dn 
    293293        raise PyKotaStorageError, message 
    294              
     294 
    295295    def filterNames(self, records, attribute, patterns=None) : 
    296296        """Returns a list of 'attribute' from a list of records. 
    297          
     297 
    298298           Logs any missing attribute. 
    299         """    
     299        """ 
    300300        result = [] 
    301301        for (dn, record) in records : 
     
    308308                    if (not isinstance(patterns, type([]))) and (not isinstance(patterns, type(()))) : 
    309309                        patterns = [ patterns ] 
    310                     if self.tool.matchString(attrval, patterns) :    
     310                    if self.tool.matchString(attrval, patterns) : 
    311311                        result.append(attrval) 
    312                 else :     
     312                else : 
    313313                    result.append(attrval) 
    314         return result         
    315                  
    316     def getAllBillingCodes(self, billingcode=None) :     
     314        return result 
     315 
     316    def getAllBillingCodes(self, billingcode=None) : 
    317317        """Extracts all billing codes or only the billing codes matching the optional parameter.""" 
    318318        ldapfilter = "objectClass=pykotaBilling" 
     
    320320        if result : 
    321321            return [self.databaseToUserCharset(bc) for bc in self.filterNames(result, "pykotaBillingCode", billingcode)] 
    322         else :     
     322        else : 
    323323            return [] 
    324          
    325     def getAllPrintersNames(self, printername=None) :     
     324 
     325    def getAllPrintersNames(self, printername=None) : 
    326326        """Extracts all printer names or only the printers' names matching the optional parameter.""" 
    327327        ldapfilter = "objectClass=pykotaPrinter" 
     
    329329        if result : 
    330330            return self.filterNames(result, "pykotaPrinterName", printername) 
    331         else :     
     331        else : 
    332332            return [] 
    333          
    334     def getAllUsersNames(self, username=None) :     
     333 
     334    def getAllUsersNames(self, username=None) : 
    335335        """Extracts all user names or only the users' names matching the optional parameter.""" 
    336336        ldapfilter = "objectClass=pykotaAccount" 
     
    338338        if result : 
    339339            return self.filterNames(result, "pykotaUserName", username) 
    340         else :     
     340        else : 
    341341            return [] 
    342          
    343     def getAllGroupsNames(self, groupname=None) :     
     342 
     343    def getAllGroupsNames(self, groupname=None) : 
    344344        """Extracts all group names or only the groups' names matching the optional parameter.""" 
    345345        ldapfilter = "objectClass=pykotaGroup" 
     
    347347        if result : 
    348348            return self.filterNames(result, "pykotaGroupName", groupname) 
    349         else :     
     349        else : 
    350350            return [] 
    351          
     351 
    352352    def getUserNbJobsFromHistory(self, user) : 
    353353        """Returns the number of jobs the user has in history.""" 
    354354        result = self.doSearch("(&(pykotaUserName=%s)(objectClass=pykotaJob))" % self.userCharsetToDatabase(user.Name), None, base=self.info["jobbase"]) 
    355355        return len(result) 
    356          
    357     def getUserFromBackend(self, username) :     
     356 
     357    def getUserFromBackend(self, username) : 
    358358        """Extracts user information given its name.""" 
    359359        user = StorageUser(self, username) 
     
    376376                    if user.AccountBalance[0].upper() == "NONE" : 
    377377                        user.AccountBalance = None 
    378                     else :     
     378                    else : 
    379379                        user.AccountBalance = float(user.AccountBalance[0]) 
    380                 user.AccountBalance = user.AccountBalance or 0.0         
     380                user.AccountBalance = user.AccountBalance or 0.0 
    381381                user.LifeTimePaid = fields.get("pykotaLifeTimePaid") 
    382382                user.OverCharge = float(fields.get("pykotaOverCharge", [1.0])[0]) 
     
    384384                    if user.LifeTimePaid[0].upper() == "NONE" : 
    385385                        user.LifeTimePaid = None 
    386                     else :     
     386                    else : 
    387387                        user.LifeTimePaid = float(user.LifeTimePaid[0]) 
    388                 user.LifeTimePaid = user.LifeTimePaid or 0.0         
     388                user.LifeTimePaid = user.LifeTimePaid or 0.0 
    389389                user.Payments = [] 
    390390                for payment in fields.get("pykotaPayments", []) : 
     
    395395                        (date, amount) = payment.split(" # ") 
    396396                        description = "" 
    397                     else :     
     397                    else : 
    398398                        description = self.databaseToUserCharset(base64.decodestring(description)) 
    399399                    user.Payments.append((date, float(amount), description)) 
    400400            user.Exists = True 
    401401        return user 
    402         
    403     def getGroupFromBackend(self, groupname) :     
     402 
     403    def getGroupFromBackend(self, groupname) : 
    404404        """Extracts group information given its name.""" 
    405405        group = StorageGroup(self, groupname) 
     
    409409            fields = result[0][1] 
    410410            group.ident = result[0][0] 
    411             group.Name = fields.get("pykotaGroupName", [self.databaseToUserCharset(groupname)])[0]  
     411            group.Name = fields.get("pykotaGroupName", [self.databaseToUserCharset(groupname)])[0] 
    412412            group.Description = self.databaseToUserCharset(fields.get("description", [None])[0]) 
    413413            group.LimitBy = fields.get("pykotaLimitBy", ["quota"])[0] 
     
    420420            group.Exists = True 
    421421        return group 
    422         
    423     def getPrinterFromBackend(self, printername) :         
     422 
     423    def getPrinterFromBackend(self, printername) : 
    424424        """Extracts printer information given its name : returns first matching printer.""" 
    425425        printer = StoragePrinter(self, printername) 
     
    434434            fields = result[0][1]       # take only first matching printer, ignore the rest 
    435435            printer.ident = result[0][0] 
    436             printer.Name = fields.get("pykotaPrinterName", [self.databaseToUserCharset(printername)])[0]  
     436            printer.Name = fields.get("pykotaPrinterName", [self.databaseToUserCharset(printername)])[0] 
    437437            printer.PricePerJob = float(fields.get("pykotaPricePerJob", [0.0])[0]) 
    438438            printer.PricePerPage = float(fields.get("pykotaPricePerPage", [0.0])[0]) 
     
    444444                printer.PassThrough = 0 
    445445            printer.uniqueMember = fields.get("uniqueMember", []) 
    446             printer.Description = self.databaseToUserCharset(fields.get("description", [""])[0])  
     446            printer.Description = self.databaseToUserCharset(fields.get("description", [""])[0]) 
    447447            printer.Exists = True 
    448         return printer     
    449          
    450     def getUserPQuotaFromBackend(self, user, printer) :         
     448        return printer 
     449 
     450    def getUserPQuotaFromBackend(self, user, printer) : 
    451451        """Extracts a user print quota.""" 
    452452        userpquota = StorageUserPQuota(self, user, printer) 
     
    454454            if self.info["userquotabase"].lower() == "user" : 
    455455                base = user.ident 
    456             else :     
     456            else : 
    457457                base = self.info["userquotabase"] 
    458458            result = self.doSearch("(&(objectClass=pykotaUserPQuota)(pykotaUserName=%s)(pykotaPrinterName=%s))" % \ 
     
    470470                    if userpquota.SoftLimit[0].upper() == "NONE" : 
    471471                        userpquota.SoftLimit = None 
    472                     else :     
     472                    else : 
    473473                        userpquota.SoftLimit = int(userpquota.SoftLimit[0]) 
    474474                userpquota.HardLimit = fields.get("pykotaHardLimit") 
     
    476476                    if userpquota.HardLimit[0].upper() == "NONE" : 
    477477                        userpquota.HardLimit = None 
    478                     elif userpquota.HardLimit is not None :     
     478                    elif userpquota.HardLimit is not None : 
    479479                        userpquota.HardLimit = int(userpquota.HardLimit[0]) 
    480480                userpquota.DateLimit = fields.get("pykotaDateLimit") 
    481481                if userpquota.DateLimit is not None : 
    482                     if userpquota.DateLimit[0].upper() == "NONE" :  
     482                    if userpquota.DateLimit[0].upper() == "NONE" : 
    483483                        userpquota.DateLimit = None 
    484                     else :     
     484                    else : 
    485485                        userpquota.DateLimit = userpquota.DateLimit[0] 
    486486                userpquota.MaxJobSize = fields.get("pykotaMaxJobSize") 
     
    488488                    if userpquota.MaxJobSize[0].upper() == "NONE" : 
    489489                        userpquota.MaxJobSize = None 
    490                     else :     
     490                    else : 
    491491                        userpquota.MaxJobSize = int(userpquota.MaxJobSize[0]) 
    492492                userpquota.Exists = True 
    493493        return userpquota 
    494          
    495     def getGroupPQuotaFromBackend(self, group, printer) :         
     494 
     495    def getGroupPQuotaFromBackend(self, group, printer) : 
    496496        """Extracts a group print quota.""" 
    497497        grouppquota = StorageGroupPQuota(self, group, printer) 
     
    499499            if self.info["groupquotabase"].lower() == "group" : 
    500500                base = group.ident 
    501             else :     
     501            else : 
    502502                base = self.info["groupquotabase"] 
    503503            result = self.doSearch("(&(objectClass=pykotaGroupPQuota)(pykotaGroupName=%s)(pykotaPrinterName=%s))" % \ 
     
    512512                    if grouppquota.SoftLimit[0].upper() == "NONE" : 
    513513                        grouppquota.SoftLimit = None 
    514                     else :     
     514                    else : 
    515515                        grouppquota.SoftLimit = int(grouppquota.SoftLimit[0]) 
    516516                grouppquota.HardLimit = fields.get("pykotaHardLimit") 
     
    518518                    if grouppquota.HardLimit[0].upper() == "NONE" : 
    519519                        grouppquota.HardLimit = None 
    520                     else :     
     520                    else : 
    521521                        grouppquota.HardLimit = int(grouppquota.HardLimit[0]) 
    522522                grouppquota.DateLimit = fields.get("pykotaDateLimit") 
    523523                if grouppquota.DateLimit is not None : 
    524                     if grouppquota.DateLimit[0].upper() == "NONE" :  
     524                    if grouppquota.DateLimit[0].upper() == "NONE" : 
    525525                        grouppquota.DateLimit = None 
    526                     else :     
     526                    else : 
    527527                        grouppquota.DateLimit = grouppquota.DateLimit[0] 
    528528                grouppquota.MaxJobSize = fields.get("pykotaMaxJobSize") 
     
    530530                    if grouppquota.MaxJobSize[0].upper() == "NONE" : 
    531531                        grouppquota.MaxJobSize = None 
    532                     else :     
     532                    else : 
    533533                        grouppquota.MaxJobSize = int(grouppquota.MaxJobSize[0]) 
    534534                grouppquota.PageCounter = 0 
     
    545545                                          ["pykotaPageCounter", "pykotaLifePageCounter"], base=base) 
    546546                if result : 
    547                     for userpquota in result :     
     547                    for userpquota in result : 
    548548                        grouppquota.PageCounter += int(userpquota[1].get("pykotaPageCounter", [0])[0] or 0) 
    549549                        grouppquota.LifePageCounter += int(userpquota[1].get("pykotaLifePageCounter", [0])[0] or 0) 
    550550                grouppquota.Exists = True 
    551551        return grouppquota 
    552          
    553     def getPrinterLastJobFromBackend(self, printer) :         
     552 
     553    def getPrinterLastJobFromBackend(self, printer) : 
    554554        """Extracts a printer's last job information.""" 
    555555        lastjob = StorageLastJob(self, printer) 
     
    564564            result = None 
    565565            try : 
    566                 result = self.doSearch("objectClass=pykotaJob", [ "pykotaJobSizeBytes",  
    567                                                                   "pykotaHostName",  
    568                                                                   "pykotaUserName",  
    569                                                                   "pykotaPrinterName",  
    570                                                                   "pykotaJobId",  
    571                                                                   "pykotaPrinterPageCounter",  
    572                                                                   "pykotaJobSize",  
    573                                                                   "pykotaAction",  
    574                                                                   "pykotaJobPrice",  
    575                                                                   "pykotaFileName",  
    576                                                                   "pykotaTitle",  
    577                                                                   "pykotaCopies",  
    578                                                                   "pykotaOptions",  
    579                                                                   "pykotaBillingCode",  
    580                                                                   "pykotaPages",  
    581                                                                   "pykotaMD5Sum",  
     566                result = self.doSearch("objectClass=pykotaJob", [ "pykotaJobSizeBytes", 
     567                                                                  "pykotaHostName", 
     568                                                                  "pykotaUserName", 
     569                                                                  "pykotaPrinterName", 
     570                                                                  "pykotaJobId", 
     571                                                                  "pykotaPrinterPageCounter", 
     572                                                                  "pykotaJobSize", 
     573                                                                  "pykotaAction", 
     574                                                                  "pykotaJobPrice", 
     575                                                                  "pykotaFileName", 
     576                                                                  "pykotaTitle", 
     577                                                                  "pykotaCopies", 
     578                                                                  "pykotaOptions", 
     579                                                                  "pykotaBillingCode", 
     580                                                                  "pykotaPages", 
     581                                                                  "pykotaMD5Sum", 
    582582                                                                  "pykotaPrecomputedJobSize", 
    583583                                                                  "pykotaPrecomputedJobPrice", 
    584                                                                   "createTimestamp" ],  
     584                                                                  "createTimestamp" ], 
    585585                                                                base="cn=%s,%s" % (lastjobident, self.info["jobbase"]), scope=ldap.SCOPE_BASE) 
    586             except PyKotaStorageError :     
    587                 pass # Last job entry exists, but job probably doesn't exist anymore.  
     586            except PyKotaStorageError : 
     587                pass # Last job entry exists, but job probably doesn't exist anymore. 
    588588            if result : 
    589589                fields = result[0][1] 
     
    594594                try : 
    595595                    lastjob.JobSize = int(fields.get("pykotaJobSize", [0])[0]) 
    596                 except ValueError :     
     596                except ValueError : 
    597597                    lastjob.JobSize = None 
    598                 try :     
     598                try : 
    599599                    lastjob.JobPrice = float(fields.get("pykotaJobPrice", [0.0])[0]) 
    600                 except ValueError :     
     600                except ValueError : 
    601601                    lastjob.JobPrice = None 
    602602                lastjob.JobAction = fields.get("pykotaAction", [""])[0] 
    603                 lastjob.JobFileName = self.databaseToUserCharset(fields.get("pykotaFileName", [""])[0])  
    604                 lastjob.JobTitle = self.databaseToUserCharset(fields.get("pykotaTitle", [""])[0])  
     603                lastjob.JobFileName = self.databaseToUserCharset(fields.get("pykotaFileName", [""])[0]) 
     604                lastjob.JobTitle = self.databaseToUserCharset(fields.get("pykotaTitle", [""])[0]) 
    605605                lastjob.JobCopies = int(fields.get("pykotaCopies", [0])[0]) 
    606                 lastjob.JobOptions = self.databaseToUserCharset(fields.get("pykotaOptions", [""])[0])  
     606                lastjob.JobOptions = self.databaseToUserCharset(fields.get("pykotaOptions", [""])[0]) 
    607607                lastjob.JobHostName = fields.get("pykotaHostName", [""])[0] 
    608608                lastjob.JobSizeBytes = fields.get("pykotaJobSizeBytes", [0L])[0] 
     
    612612                try : 
    613613                    lastjob.PrecomputedJobSize = int(fields.get("pykotaPrecomputedJobSize", [0])[0]) 
    614                 except ValueError :     
     614                except ValueError : 
    615615                    lastjob.PrecomputedJobSize = None 
    616                 try :     
     616                try : 
    617617                    lastjob.PrecomputedJobPrice = float(fields.get("pykotaPrecomputedJobPrice", [0.0])[0]) 
    618                 except ValueError :     
     618                except ValueError : 
    619619                    lastjob.PrecomputedJobPrice = None 
    620620                if lastjob.JobTitle == lastjob.JobFileName == lastjob.JobOptions == "hidden" : 
     
    625625                lastjob.Exists = True 
    626626        return lastjob 
    627          
    628     def getGroupMembersFromBackend(self, group) :         
     627 
     628    def getGroupMembersFromBackend(self, group) : 
    629629        """Returns the group's members list.""" 
    630630        groupmembers = [] 
     
    637637            for username in result[0][1].get(self.info["groupmembers"], []) : 
    638638                groupmembers.append(self.getUser(self.databaseToUserCharset(username))) 
    639         return groupmembers         
    640          
    641     def getUserGroupsFromBackend(self, user) :         
     639        return groupmembers 
     640 
     641    def getUserGroupsFromBackend(self, user) : 
    642642        """Returns the user's groups list.""" 
    643643        groups = [] 
     
    657657                    if group.LimitBy is not None : 
    658658                        group.LimitBy = group.LimitBy[0] 
    659                     else :     
     659                    else : 
    660660                        group.LimitBy = "quota" 
    661661                    group.AccountBalance = 0.0 
     
    668668                    self.cacheEntry("GROUPS", group.Name, group) 
    669669                groups.append(group) 
    670         return groups         
    671          
    672     def getParentPrintersFromBackend(self, printer) :     
     670        return groups 
     671 
     672    def getParentPrintersFromBackend(self, printer) : 
    673673        """Get all the printer groups this printer is a member of.""" 
    674674        pgroups = [] 
     
    684684                        pgroups.append(parentprinter) 
    685685        return pgroups 
    686          
     686 
    687687    def getMatchingPrinters(self, printerpattern) : 
    688688        """Returns the list of all printers for which name matches a certain pattern.""" 
     
    694694        if result : 
    695695            patterns = printerpattern.split(",") 
    696             try : 
    697                 patdict = {}.fromkeys(patterns) 
    698             except AttributeError :     
    699                 # Python v2.2 or earlier 
    700                 patdict = {} 
    701                 for p in patterns : 
    702                     patdict[p] = None 
     696            patdict = {}.fromkeys(patterns) 
    703697            for (printerid, fields) in result : 
    704698                printername = self.databaseToUserCharset(fields.get("pykotaPrinterName", [""])[0] or fields.get(self.info["printerrdn"], [""])[0]) 
     
    715709                        printer.PassThrough = 0 
    716710                    printer.uniqueMember = fields.get("uniqueMember", []) 
    717                     printer.Description = self.databaseToUserCharset(fields.get("description", [""])[0])  
     711                    printer.Description = self.databaseToUserCharset(fields.get("description", [""])[0]) 
    718712                    printer.Exists = True 
    719713                    printers.append(printer) 
    720714                    self.cacheEntry("PRINTERS", printer.Name, printer) 
    721         return printers         
    722          
     715        return printers 
     716 
    723717    def getMatchingUsers(self, userpattern) : 
    724718        """Returns the list of all users for which name matches a certain pattern.""" 
     
    730724        if result : 
    731725            patterns = userpattern.split(",") 
    732             try : 
    733                 patdict = {}.fromkeys(patterns) 
    734             except AttributeError :     
    735                 # Python v2.2 or earlier 
    736                 patdict = {} 
    737                 for p in patterns : 
    738                     patdict[p] = None 
     726            patdict = {}.fromkeys(patterns) 
    739727            for (userid, fields) in result : 
    740728                username = self.databaseToUserCharset(fields.get("pykotaUserName", [""])[0] or fields.get(self.info["userrdn"], [""])[0]) 
     
    744732                    user.Email = fields.get(self.info["usermail"], [None])[0] 
    745733                    user.LimitBy = fields.get("pykotaLimitBy", ["quota"])[0] 
    746                     user.Description = self.databaseToUserCharset(fields.get("description", [""])[0])  
     734                    user.Description = self.databaseToUserCharset(fields.get("description", [""])[0]) 
    747735                    uname = self.userCharsetToDatabase(username) 
    748736                    result = self.doSearch("(&(objectClass=pykotaAccountBalance)(|(pykotaUserName=%s)(%s=%s)))" % \ 
     
    760748                            if user.AccountBalance[0].upper() == "NONE" : 
    761749                                user.AccountBalance = None 
    762                             else :     
     750                            else : 
    763751                                user.AccountBalance = float(user.AccountBalance[0]) 
    764                         user.AccountBalance = user.AccountBalance or 0.0         
     752                        user.AccountBalance = user.AccountBalance or 0.0 
    765753                        user.LifeTimePaid = fields.get("pykotaLifeTimePaid") 
    766754                        if user.LifeTimePaid is not None : 
    767755                            if user.LifeTimePaid[0].upper() == "NONE" : 
    768756                                user.LifeTimePaid = None 
    769                             else :     
     757                            else : 
    770758                                user.LifeTimePaid = float(user.LifeTimePaid[0]) 
    771                         user.LifeTimePaid = user.LifeTimePaid or 0.0         
     759                        user.LifeTimePaid = user.LifeTimePaid or 0.0 
    772760                        user.Payments = [] 
    773761                        for payment in fields.get("pykotaPayments", []) : 
     
    778766                                (date, amount) = payment.split(" # ") 
    779767                                description = "" 
    780                             else :     
     768                            else : 
    781769                                description = self.databaseToUserCharset(base64.decodestring(description)) 
    782770                            user.Payments.append((date, float(amount), description)) 
     
    784772                    users.append(user) 
    785773                    self.cacheEntry("USERS", user.Name, user) 
    786         return users        
    787          
     774        return users 
     775 
    788776    def getMatchingGroups(self, grouppattern) : 
    789777        """Returns the list of all groups for which name matches a certain pattern.""" 
     
    795783        if result : 
    796784            patterns = grouppattern.split(",") 
    797             try : 
    798                 patdict = {}.fromkeys(patterns) 
    799             except AttributeError :     
    800                 # Python v2.2 or earlier 
    801                 patdict = {} 
    802                 for p in patterns : 
    803                     patdict[p] = None 
     785            patdict = {}.fromkeys(patterns) 
    804786            for (groupid, fields) in result : 
    805787                groupname = self.databaseToUserCharset(fields.get("pykotaGroupName", [""])[0] or fields.get(self.info["grouprdn"], [""])[0]) 
     
    807789                    group = StorageGroup(self, groupname) 
    808790                    group.ident = groupid 
    809                     group.Name = fields.get("pykotaGroupName", [self.databaseToUserCharset(groupname)])[0]  
     791                    group.Name = fields.get("pykotaGroupName", [self.databaseToUserCharset(groupname)])[0] 
    810792                    group.LimitBy = fields.get("pykotaLimitBy", ["quota"])[0] 
    811                     group.Description = self.databaseToUserCharset(fields.get("description", [""])[0])  
     793                    group.Description = self.databaseToUserCharset(fields.get("description", [""])[0]) 
    812794                    group.AccountBalance = 0.0 
    813795                    group.LifeTimePaid = 0.0 
     
    820802                    self.cacheEntry("GROUPS", group.Name, group) 
    821803        return groups 
    822          
    823     def getPrinterUsersAndQuotas(self, printer, names=["*"]) :         
     804 
     805    def getPrinterUsersAndQuotas(self, printer, names=["*"]) : 
    824806        """Returns the list of users who uses a given printer, along with their quotas.""" 
    825807        usersandquotas = [] 
     
    846828                    if userpquota.SoftLimit[0].upper() == "NONE" : 
    847829                        userpquota.SoftLimit = None 
    848                     else :     
     830                    else : 
    849831                        userpquota.SoftLimit = int(userpquota.SoftLimit[0]) 
    850832                userpquota.HardLimit = fields.get("pykotaHardLimit") 
     
    852834                    if userpquota.HardLimit[0].upper() == "NONE" : 
    853835                        userpquota.HardLimit = None 
    854                     elif userpquota.HardLimit is not None :     
     836                    elif userpquota.HardLimit is not None : 
    855837                        userpquota.HardLimit = int(userpquota.HardLimit[0]) 
    856838                userpquota.DateLimit = fields.get("pykotaDateLimit") 
    857839                if userpquota.DateLimit is not None : 
    858                     if userpquota.DateLimit[0].upper() == "NONE" :  
     840                    if userpquota.DateLimit[0].upper() == "NONE" : 
    859841                        userpquota.DateLimit = None 
    860                     else :     
     842                    else : 
    861843                        userpquota.DateLimit = userpquota.DateLimit[0] 
    862844                userpquota.Exists = True 
    863845                usersandquotas.append((user, userpquota)) 
    864846                self.cacheEntry("USERPQUOTAS", "%s@%s" % (user.Name, printer.Name), userpquota) 
    865         usersandquotas.sort(lambda x, y : cmp(x[0].Name, y[0].Name))             
     847        usersandquotas.sort(lambda x, y : cmp(x[0].Name, y[0].Name)) 
    866848        return usersandquotas 
    867                  
    868     def getPrinterGroupsAndQuotas(self, printer, names=["*"]) :         
     849 
     850    def getPrinterGroupsAndQuotas(self, printer, names=["*"]) : 
    869851        """Returns the list of groups which uses a given printer, along with their quotas.""" 
    870852        groupsandquotas = [] 
     
    884866                grouppquota = self.getGroupPQuota(group, printer) 
    885867                groupsandquotas.append((group, grouppquota)) 
    886         groupsandquotas.sort(lambda x, y : cmp(x[0].Name, y[0].Name))             
     868        groupsandquotas.sort(lambda x, y : cmp(x[0].Name, y[0].Name)) 
    887869        return groupsandquotas 
    888          
     870 
    889871    def addPrinter(self, printer) : 
    890872        """Adds a printer to the quota storage, returns the old value if it already exists.""" 
     
    902884                   "pykotaPricePerPage" : str(printer.PricePerPage or 0.0), 
    903885                   "pykotaPricePerJob" : str(printer.PricePerJob or 0.0), 
    904                  }  
     886                 } 
    905887        dn = "%s=%s,%s" % (self.info["printerrdn"], printername, self.info["printerbase"]) 
    906888        self.doAdd(dn, fields) 
    907889        printer.isDirty = False 
    908890        return None # the entry created doesn't need further modification 
    909          
    910     def addUser(self, user) :         
     891 
     892    def addUser(self, user) : 
    911893        """Adds a user to the quota storage, returns the old value if it already exists.""" 
    912894        oldentry = self.getUser(user.Name) 
     
    919901                       "description" : self.userCharsetToDatabase(user.Description or ""), 
    920902                       self.info["usermail"] : user.Email or "", 
    921                     }    
    922                         
     903                    } 
     904 
    923905        mustadd = 1 
    924906        if self.info["newuser"].lower() != 'below' : 
     
    938920                fields.update({ "pykotaBalance" : str(user.AccountBalance or 0.0), 
    939921                                "pykotaOverCharge" : str(user.OverCharge), 
    940                                 "pykotaLifeTimePaid" : str(user.LifeTimePaid or 0.0), })    
     922                                "pykotaLifeTimePaid" : str(user.LifeTimePaid or 0.0), }) 
    941923                self.doModify(dn, fields) 
    942924                mustadd = 0 
    943925            else : 
    944926                message = _("Unable to find an existing objectClass %s entry with %s=%s to attach pykotaAccount objectClass") % (where, self.info["userrdn"], user.Name) 
    945                 if action.lower() == "warn" :     
     927                if action.lower() == "warn" : 
    946928                    self.tool.printInfo(_("%s. A new entry will be created instead.") % message, "warn") 
    947929                else : # 'fail' or incorrect setting 
    948930                    raise PyKotaStorageError, "%s. Action aborted. Please check your configuration." % message 
    949                  
     931 
    950932        if mustadd : 
    951             if self.info["userbase"] == self.info["balancebase"] :             
     933            if self.info["userbase"] == self.info["balancebase"] : 
    952934                fields = { self.info["userrdn"] : uname, 
    953935                           "objectClass" : ["pykotaObject", "pykotaAccount", "pykotaAccountBalance"], 
     
    955937                           "pykotaBalance" : str(user.AccountBalance or 0.0), 
    956938                           "pykotaOverCharge" : str(user.OverCharge), 
    957                            "pykotaLifeTimePaid" : str(user.LifeTimePaid or 0.0),  
    958                          }  
    959             else :              
     939                           "pykotaLifeTimePaid" : str(user.LifeTimePaid or 0.0), 
     940                         } 
     941            else : 
    960942                fields = { self.info["userrdn"] : uname, 
    961943                           "objectClass" : ["pykotaObject", "pykotaAccount"], 
    962944                           "cn" : uname, 
    963                          }  
    964             fields.update(newfields)          
     945                         } 
     946            fields.update(newfields) 
    965947            dn = "%s=%s,%s" % (self.info["userrdn"], uname, self.info["userbase"]) 
    966948            self.doAdd(dn, fields) 
    967             if self.info["userbase"] != self.info["balancebase"] :             
     949            if self.info["userbase"] != self.info["balancebase"] : 
    968950                fields = { self.info["balancerdn"] : uname, 
    969951                           "objectClass" : ["pykotaObject", "pykotaAccountBalance"], 
     
    971953                           "pykotaBalance" : str(user.AccountBalance or 0.0), 
    972954                           "pykotaOverCharge" : str(user.OverCharge), 
    973                            "pykotaLifeTimePaid" : str(user.LifeTimePaid or 0.0),   
    974                          }  
     955                           "pykotaLifeTimePaid" : str(user.LifeTimePaid or 0.0), 
     956                         } 
    975957                dn = "%s=%s,%s" % (self.info["balancerdn"], uname, self.info["balancebase"]) 
    976958                self.doAdd(dn, fields) 
     
    982964        user.isDirty = False 
    983965        return None # the entry created doesn't need further modification 
    984          
    985     def addGroup(self, group) :         
     966 
     967    def addGroup(self, group) : 
    986968        """Adds a group to the quota storage, returns the old value if it already exists.""" 
    987969        oldentry = self.getGroup(group.Name) 
     
    989971            return oldentry # we return the existing entry 
    990972        gname = self.userCharsetToDatabase(group.Name) 
    991         newfields = {  
     973        newfields = { 
    992974                      "pykotaGroupName" : gname, 
    993975                      "pykotaLimitBy" : (group.LimitBy or "quota"), 
    994976                      "description" : self.userCharsetToDatabase(group.Description or "") 
    995                     }  
     977                    } 
    996978        mustadd = 1 
    997979        if self.info["newgroup"].lower() != 'below' : 
     
    1013995            else : 
    1014996                message = _("Unable to find an existing entry to attach pykotaGroup objectclass %s") % group.Name 
    1015                 if action.lower() == "warn" :     
     997                if action.lower() == "warn" : 
    1016998                    self.tool.printInfo("%s. A new entry will be created instead." % message, "warn") 
    1017999                else : # 'fail' or incorrect setting 
    10181000                    raise PyKotaStorageError, "%s. Action aborted. Please check your configuration." % message 
    1019                  
     1001 
    10201002        if mustadd : 
    10211003            fields = { self.info["grouprdn"] : gname, 
    10221004                       "objectClass" : ["pykotaObject", "pykotaGroup"], 
    10231005                       "cn" : gname, 
    1024                      }  
    1025             fields.update(newfields)          
     1006                     } 
     1007            fields.update(newfields) 
    10261008            dn = "%s=%s,%s" % (self.info["grouprdn"], gname, self.info["groupbase"]) 
    10271009            self.doAdd(dn, fields) 
    10281010        group.isDirty = False 
    10291011        return None # the entry created doesn't need further modification 
    1030          
    1031     def addUserToGroup(self, user, group) :     
     1012 
     1013    def addUserToGroup(self, user, group) : 
    10321014        """Adds an user to a group.""" 
    10331015        if user.Name not in [u.Name for u in self.getGroupMembers(group)] : 
    1034             result = self.doSearch("objectClass=pykotaGroup", None, base=group.ident, scope=ldap.SCOPE_BASE)     
     1016            result = self.doSearch("objectClass=pykotaGroup", None, base=group.ident, scope=ldap.SCOPE_BASE) 
    10351017            if result : 
    10361018                fields = result[0][1] 
     
    10401022                self.doModify(group.ident, fields) 
    10411023                group.Members.append(user) 
    1042                  
    1043     def delUserFromGroup(self, user, group) :     
     1024 
     1025    def delUserFromGroup(self, user, group) : 
    10441026        """Removes an user from a group.""" 
    10451027        if user.Name in [u.Name for u in self.getGroupMembers(group)] : 
     
    10491031                if not fields.has_key(self.info["groupmembers"]) : 
    10501032                    fields[self.info["groupmembers"]] = [] 
    1051                 try :     
     1033                try : 
    10521034                    fields[self.info["groupmembers"]].remove(self.userCharsetToDatabase(user.Name)) 
    10531035                except ValueError : 
     
    10561038                    self.doModify(group.ident, fields) 
    10571039                    group.Members.remove(user) 
    1058                  
     1040 
    10591041    def addUserPQuota(self, upq) : 
    10601042        """Initializes a user print quota on a printer.""" 
     
    10771059                   "pykotaWarnCount" : str(upq.WarnCount or 0), 
    10781060                   "pykotaMaxJobSize" : str(upq.MaxJobSize or 0), 
    1079                  }  
     1061                 } 
    10801062        if self.info["userquotabase"].lower() == "user" : 
    10811063            dn = "cn=%s,%s" % (uuid, upq.User.ident) 
    1082         else :     
     1064        else : 
    10831065            dn = "cn=%s,%s" % (uuid, self.info["userquotabase"]) 
    10841066        self.doAdd(dn, fields) 
    10851067        upq.isDirty = False 
    10861068        return None # the entry created doesn't need further modification 
    1087          
     1069 
    10881070    def addGroupPQuota(self, gpq) : 
    10891071        """Initializes a group print quota on a printer.""" 
     
    10991081                   "pykotaPrinterName" : pname, 
    11001082                   "pykotaDateLimit" : "None", 
    1101                  }  
     1083                 } 
    11021084        if self.info["groupquotabase"].lower() == "group" : 
    11031085            dn = "cn=%s,%s" % (uuid, gpq.Group.ident) 
    1104         else :     
     1086        else : 
    11051087            dn = "cn=%s,%s" % (uuid, self.info["groupquotabase"]) 
    11061088        self.doAdd(dn, fields) 
    11071089        gpq.isDirty = False 
    11081090        return None # the entry created doesn't need further modification 
    1109          
    1110     def savePrinter(self, printer) :     
     1091 
     1092    def savePrinter(self, printer) : 
    11111093        """Saves the printer to the database in a single operation.""" 
    11121094        fields = { 
     
    11181100                 } 
    11191101        self.doModify(printer.ident, fields) 
    1120          
     1102 
    11211103    def saveUser(self, user) : 
    11221104        """Saves the user to the database in a single operation.""" 
    11231105        newfields = { 
    11241106                       "pykotaLimitBy" : (user.LimitBy or "quota"), 
    1125                        "description" : self.userCharsetToDatabase(user.Description or ""),  
     1107                       "description" : self.userCharsetToDatabase(user.Description or ""), 
    11261108                       self.info["usermail"] : user.Email or "", 
    1127                     }    
     1109                    } 
    11281110        self.doModify(user.ident, newfields) 
    1129          
     1111 
    11301112        newfields = { "pykotaBalance" : str(user.AccountBalance or 0.0), 
    1131                       "pykotaLifeTimePaid" : str(user.LifeTimePaid or 0.0),  
     1113                      "pykotaLifeTimePaid" : str(user.LifeTimePaid or 0.0), 
    11321114                      "pykotaOverCharge" : str(user.OverCharge), 
    11331115                    } 
    11341116        self.doModify(user.idbalance, newfields) 
    1135          
     1117 
    11361118    def saveGroup(self, group) : 
    11371119        """Saves the group to the database in a single operation.""" 
    11381120        newfields = { 
    11391121                       "pykotaLimitBy" : (group.LimitBy or "quota"), 
    1140                        "description" : self.userCharsetToDatabase(group.Description or ""),  
    1141                     }    
     1122                       "description" : self.userCharsetToDatabase(group.Description or ""), 
     1123                    } 
    11421124        self.doModify(group.ident, newfields) 
    1143          
    1144     def writeUserPQuotaDateLimit(self, userpquota, datelimit) :     
     1125 
     1126    def writeUserPQuotaDateLimit(self, userpquota, datelimit) : 
    11451127        """Sets the date limit permanently for a user print quota.""" 
    11461128        fields = { 
     
    11481130                 } 
    11491131        return self.doModify(userpquota.ident, fields) 
    1150              
    1151     def writeGroupPQuotaDateLimit(self, grouppquota, datelimit) :     
     1132 
     1133    def writeGroupPQuotaDateLimit(self, grouppquota, datelimit) : 
    11521134        """Sets the date limit permanently for a group print quota.""" 
    11531135        fields = { 
     
    11551137                 } 
    11561138        return self.doModify(grouppquota.ident, fields) 
    1157          
    1158     def increaseUserPQuotaPagesCounters(self, userpquota, nbpages) :     
     1139 
     1140    def increaseUserPQuotaPagesCounters(self, userpquota, nbpages) : 
    11591141        """Increase page counters for a user print quota.""" 
    11601142        fields = { 
     
    11621144                   "pykotaLifePageCounter" : { "operator" : "+", "value" : nbpages, "convert" : int }, 
    11631145                 } 
    1164         return self.doModify(userpquota.ident, fields)          
    1165          
    1166     def decreaseUserAccountBalance(self, user, amount) :     
     1146        return self.doModify(userpquota.ident, fields) 
     1147 
     1148    def decreaseUserAccountBalance(self, user, amount) : 
    11671149        """Decreases user's account balance from an amount.""" 
    11681150        fields = { 
    11691151                   "pykotaBalance" : { "operator" : "-", "value" : amount, "convert" : float }, 
    11701152                 } 
    1171         return self.doModify(user.idbalance, fields, flushcache=1)          
    1172         
     1153        return self.doModify(user.idbalance, fields, flushcache=1) 
     1154 
    11731155    def writeNewPayment(self, user, amount, comment="") : 
    11741156        """Adds a new payment to the payments history.""" 
     
    11801162                   "pykotaPayments" : payments, 
    11811163                 } 
    1182         return self.doModify(user.idbalance, fields)          
    1183          
    1184     def writeLastJobSize(self, lastjob, jobsize, jobprice) :         
     1164        return self.doModify(user.idbalance, fields) 
     1165 
     1166    def writeLastJobSize(self, lastjob, jobsize, jobprice) : 
    11851167        """Sets the last job's size permanently.""" 
    11861168        fields = { 
     
    11881170                   "pykotaJobPrice" : str(jobprice), 
    11891171                 } 
    1190         self.doModify(lastjob.ident, fields)          
    1191          
     1172        self.doModify(lastjob.ident, fields) 
     1173 
    11921174    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) : 
    11931175        """Adds a job in a printer's history.""" 
     
    11971179            uuid = self.genUUID() 
    11981180            dn = "cn=%s,%s" % (uuid, self.info["jobbase"]) 
    1199         else :     
     1181        else : 
    12001182            uuid = printer.LastJob.ident[3:].split(",")[0] 
    12011183            dn = printer.LastJob.ident 
    1202         if self.privacy :     
     1184        if self.privacy : 
    12031185            # For legal reasons, we want to hide the title, filename and options 
    12041186            title = filename = options = "hidden" 
     
    12111193                   "pykotaPrinterPageCounter" : str(pagecounter), 
    12121194                   "pykotaAction" : action, 
    1213                    "pykotaFileName" : ((filename is None) and "None") or self.userCharsetToDatabase(filename),  
    1214                    "pykotaTitle" : ((title is None) and "None") or self.userCharsetToDatabase(title),  
    1215                    "pykotaCopies" : str(copies),  
    1216                    "pykotaOptions" : ((options is None) and "None") or self.userCharsetToDatabase(options),  
    1217                    "pykotaHostName" : str(clienthost),  
     1195                   "pykotaFileName" : ((filename is None) and "None") or self.userCharsetToDatabase(filename), 
     1196                   "pykotaTitle" : ((title is None) and "None") or self.userCharsetToDatabase(title), 
     1197                   "pykotaCopies" : str(copies), 
     1198                   "pykotaOptions" : ((options is None) and "None") or self.userCharsetToDatabase(options), 
     1199                   "pykotaHostName" : str(clienthost), 
    12181200                   "pykotaJobSizeBytes" : str(jobsizebytes), 
    12191201                   "pykotaMD5Sum" : str(jobmd5sum), 
     
    12241206                 } 
    12251207        if (not self.disablehistory) or (not printer.LastJob.Exists) : 
    1226             if jobsize is not None :          
     1208            if jobsize is not None : 
    12271209                fields.update({ "pykotaJobSize" : str(jobsize), "pykotaJobPrice" : str(jobprice) }) 
    12281210            self.doAdd(dn, fields) 
    1229         else :     
     1211        else : 
    12301212            # here we explicitly want to reset jobsize to 'None' if needed 
    12311213            fields.update({ "pykotaJobSize" : str(jobsize), "pykotaJobPrice" : str(jobprice) }) 
    12321214            self.doModify(dn, fields) 
    1233              
     1215 
    12341216        if printer.LastJob.Exists : 
    12351217            fields = { 
    12361218                       "pykotaLastJobIdent" : uuid, 
    12371219                     } 
    1238             self.doModify(printer.LastJob.lastjobident, fields)          
    1239         else :     
     1220            self.doModify(printer.LastJob.lastjobident, fields) 
     1221        else : 
    12401222            lastjuuid = self.genUUID() 
    12411223            lastjdn = "cn=%s,%s" % (lastjuuid, self.info["lastjobbase"]) 
     
    12451227                       "pykotaPrinterName" : pname, 
    12461228                       "pykotaLastJobIdent" : uuid, 
    1247                      }   
    1248             self.doAdd(lastjdn, fields)           
    1249              
     1229                     } 
     1230            self.doAdd(lastjdn, fields) 
     1231 
    12501232    def saveUserPQuota(self, userpquota) : 
    12511233        """Saves an user print quota entry.""" 
    1252         fields = {  
     1234        fields = { 
    12531235                   "pykotaSoftLimit" : str(userpquota.SoftLimit), 
    12541236                   "pykotaHardLimit" : str(userpquota.HardLimit), 
     
    12601242                 } 
    12611243        self.doModify(userpquota.ident, fields) 
    1262          
     1244 
    12631245    def writeUserPQuotaWarnCount(self, userpquota, warncount) : 
    12641246        """Sets the warn counter value for a user quota.""" 
    1265         fields = {  
     1247        fields = { 
    12661248                   "pykotaWarnCount" : str(warncount or 0), 
    12671249                 } 
    12681250        self.doModify(userpquota.ident, fields) 
    1269          
     1251 
    12701252    def increaseUserPQuotaWarnCount(self, userpquota) : 
    12711253        """Increases the warn counter value for a user quota.""" 
     
    12731255                   "pykotaWarnCount" : { "operator" : "+", "value" : 1, "convert" : int }, 
    12741256                 } 
    1275         return self.doModify(userpquota.ident, fields)          
    1276          
     1257        return self.doModify(userpquota.ident, fields) 
     1258 
    12771259    def saveGroupPQuota(self, grouppquota) : 
    12781260        """Saves a group print quota entry.""" 
    1279         fields = {  
     1261        fields = { 
    12801262                   "pykotaSoftLimit" : str(grouppquota.SoftLimit), 
    12811263                   "pykotaHardLimit" : str(grouppquota.HardLimit), 
     
    12841266                 } 
    12851267        self.doModify(grouppquota.ident, fields) 
    1286              
     1268 
    12871269    def writePrinterToGroup(self, pgroup, printer) : 
    12881270        """Puts a printer into a printer group.""" 
     
    12911273            fields = { 
    12921274                       "uniqueMember" : pgroup.uniqueMember 
    1293                      }   
    1294             self.doModify(pgroup.ident, fields)          
    1295              
     1275                     } 
     1276            self.doModify(pgroup.ident, fields) 
     1277 
    12961278    def removePrinterFromGroup(self, pgroup, printer) : 
    12971279        """Removes a printer from a printer group.""" 
    12981280        try : 
    12991281            pgroup.uniqueMember.remove(printer.ident) 
    1300         except ValueError :     
     1282        except ValueError : 
    13011283            pass 
    1302         else :     
     1284        else : 
    13031285            fields = { 
    13041286                       "uniqueMember" : pgroup.uniqueMember, 
    1305                      }   
    1306             self.doModify(pgroup.ident, fields)          
    1307              
     1287                     } 
     1288            self.doModify(pgroup.ident, fields) 
     1289 
    13081290    def retrieveHistory(self, user=None, printer=None, hostname=None, billingcode=None, jobid=None, limit=100, start=None, end=None) : 
    13091291        """Retrieves all print jobs for user on printer (or all) between start and end date, limited to first 100 results.""" 
     
    13201302        if jobid is not None : 
    13211303            where.append("(pykotaJobId=%s)" % jobid) # TODO : jobid is text, so self.userCharsetToDatabase(jobid) but do all of them as well. 
    1322         if where :     
     1304        if where : 
    13231305            where = "(&%s)" % "".join([precond] + where) 
    1324         else :     
     1306        else : 
    13251307            where = precond 
    1326         jobs = []     
    1327         result = self.doSearch(where, fields=[ "pykotaJobSizeBytes",  
    1328                                                "pykotaHostName",  
    1329                                                "pykotaUserName",  
    1330                                                "pykotaPrinterName",  
    1331                                                "pykotaJobId",  
    1332                                                "pykotaPrinterPageCounter",  
    1333                                                "pykotaAction",  
    1334                                                "pykotaJobSize",  
    1335                                                "pykotaJobPrice",  
    1336                                                "pykotaFileName",  
    1337                                                "pykotaTitle",  
    1338                                                "pykotaCopies",  
    1339                                                "pykotaOptions",  
    1340                                                "pykotaBillingCode",  
    1341                                                "pykotaPages",  
    1342                                                "pykotaMD5Sum",  
     1308        jobs = [] 
     1309        result = self.doSearch(where, fields=[ "pykotaJobSizeBytes", 
     1310                                               "pykotaHostName", 
     1311                                               "pykotaUserName", 
     1312                                               "pykotaPrinterName", 
     1313                                               "pykotaJobId", 
     1314                                               "pykotaPrinterPageCounter", 
     1315                                               "pykotaAction", 
     1316                                               "pykotaJobSize", 
     1317                                               "pykotaJobPrice", 
     1318                                               "pykotaFileName", 
     1319                                               "pykotaTitle", 
     1320                                               "pykotaCopies", 
     1321                                               "pykotaOptions", 
     1322                                               "pykotaBillingCode", 
     1323                                               "pykotaPages", 
     1324                                               "pykotaMD5Sum", 
    13431325                                               "pykotaPrecomputedJobSize", 
    13441326                                               "pykotaPrecomputedJobPrice", 
    1345                                                "createTimestamp" ],  
     1327                                               "createTimestamp" ], 
    13461328                                      base=self.info["jobbase"]) 
    13471329        if result : 
     
    13531335                try : 
    13541336                    job.JobSize = int(fields.get("pykotaJobSize", [0])[0]) 
    1355                 except ValueError :     
     1337                except ValueError : 
    13561338                    job.JobSize = None 
    1357                 try :     
     1339                try : 
    13581340                    job.JobPrice = float(fields.get("pykotaJobPrice", [0.0])[0]) 
    13591341                except ValueError : 
    13601342                    job.JobPrice = None 
    13611343                job.JobAction = fields.get("pykotaAction", [""])[0] 
    1362                 job.JobFileName = self.databaseToUserCharset(fields.get("pykotaFileName", [""])[0])  
    1363                 job.JobTitle = self.databaseToUserCharset(fields.get("pykotaTitle", [""])[0])  
     1344                job.JobFileName = self.databaseToUserCharset(fields.get("pykotaFileName", [""])[0]) 
     1345                job.JobTitle = self.databaseToUserCharset(fields.get("pykotaTitle", [""])[0]) 
    13641346                job.JobCopies = int(fields.get("pykotaCopies", [0])[0]) 
    1365                 job.JobOptions = self.databaseToUserCharset(fields.get("pykotaOptions", [""])[0])  
     1347                job.JobOptions = self.databaseToUserCharset(fields.get("pykotaOptions", [""])[0]) 
    13661348                job.JobHostName = fields.get("pykotaHostName", [""])[0] 
    13671349                job.JobSizeBytes = fields.get("pykotaJobSizeBytes", [0L])[0] 
     
    13711353                try : 
    13721354                    job.PrecomputedJobSize = int(fields.get("pykotaPrecomputedJobSize", [0])[0]) 
    1373                 except ValueError :     
     1355                except ValueError : 
    13741356                    job.PrecomputedJobSize = None 
    1375                 try :     
     1357                try : 
    13761358                    job.PrecomputedJobPrice = float(fields.get("pykotaPrecomputedJobPrice", [0.0])[0]) 
    13771359                except ValueError : 
     
    13901372                    job.Exists = True 
    13911373                    jobs.append(job) 
    1392             jobs.sort(lambda x, y : cmp(y.JobDate, x.JobDate))         
    1393             if limit :     
     1374            jobs.sort(lambda x, y : cmp(y.JobDate, x.JobDate)) 
     1375            if limit : 
    13941376                jobs = jobs[:int(limit)] 
    13951377        return jobs 
    1396          
    1397     def deleteUser(self, user) :     
     1378 
     1379    def deleteUser(self, user) : 
    13981380        """Completely deletes an user from the Quota Storage.""" 
    13991381        uname = self.userCharsetToDatabase(user.Name) 
    1400         todelete = []     
     1382        todelete = [] 
    14011383        result = self.doSearch("(&(objectClass=pykotaJob)(pykotaUserName=%s))" % uname, base=self.info["jobbase"]) 
    14021384        for (ident, fields) in result : 
     
    14121394            # ensure the user print quota entry will be deleted 
    14131395            todelete.append(ident) 
    1414              
     1396 
    14151397            # if last job of current printer was printed by the user 
    14161398            # to delete, we also need to delete the printer's last job entry. 
     
    14181400            if printer.LastJob.UserName == user.Name : 
    14191401                todelete.append(printer.LastJob.lastjobident) 
    1420              
    1421         for ident in todelete :     
     1402 
     1403        for ident in todelete : 
    14221404            self.doDelete(ident) 
    1423              
    1424         result = self.doSearch("objectClass=pykotaAccount", None, base=user.ident, scope=ldap.SCOPE_BASE)     
     1405 
     1406        result = self.doSearch("objectClass=pykotaAccount", None, base=user.ident, scope=ldap.SCOPE_BASE) 
    14251407        if result : 
    14261408            fields = result[0][1] 
     
    14281410                if k.startswith("pykota") : 
    14291411                    del fields[k] 
    1430                 elif k.lower() == "objectclass" :     
     1412                elif k.lower() == "objectclass" : 
    14311413                    todelete = [] 
    14321414                    for i in range(len(fields[k])) : 
    1433                         if fields[k][i].startswith("pykota") :  
     1415                        if fields[k][i].startswith("pykota") : 
    14341416                            todelete.append(i) 
    1435                     todelete.sort()         
     1417                    todelete.sort() 
    14361418                    todelete.reverse() 
    14371419                    for i in todelete : 
    14381420                        del fields[k][i] 
    14391421            if fields.get("objectClass") or fields.get("objectclass") : 
    1440                 self.doModify(user.ident, fields, ignoreold=0)         
    1441             else :     
     1422                self.doModify(user.ident, fields, ignoreold=0) 
     1423            else : 
    14421424                self.doDelete(user.ident) 
    14431425        result = self.doSearch("(&(objectClass=pykotaAccountBalance)(pykotaUserName=%s))" % \ 
     
    14471429        for (ident, fields) in result : 
    14481430            self.doDelete(ident) 
    1449          
    1450     def deleteGroup(self, group) :     
     1431 
     1432    def deleteGroup(self, group) : 
    14511433        """Completely deletes a group from the Quota Storage.""" 
    14521434        gname = self.userCharsetToDatabase(group.Name) 
     
    14611443        for (ident, fields) in result : 
    14621444            self.doDelete(ident) 
    1463         result = self.doSearch("objectClass=pykotaGroup", None, base=group.ident, scope=ldap.SCOPE_BASE)     
     1445        result = self.doSearch("objectClass=pykotaGroup", None, base=group.ident, scope=ldap.SCOPE_BASE) 
    14641446        if result : 
    14651447            fields = result[0][1] 
     
    14671449                if k.startswith("pykota") : 
    14681450                    del fields[k] 
    1469                 elif k.lower() == "objectclass" :     
     1451                elif k.lower() == "objectclass" : 
    14701452                    todelete = [] 
    14711453                    for i in range(len(fields[k])) : 
    1472                         if fields[k][i].startswith("pykota") :  
     1454                        if fields[k][i].startswith("pykota") : 
    14731455                            todelete.append(i) 
    1474                     todelete.sort()         
     1456                    todelete.sort() 
    14751457                    todelete.reverse() 
    14761458                    for i in todelete : 
    14771459                        del fields[k][i] 
    14781460            if fields.get("objectClass") or fields.get("objectclass") : 
    1479                 self.doModify(group.ident, fields, ignoreold=0)         
    1480             else :     
     1461                self.doModify(group.ident, fields, ignoreold=0) 
     1462            else : 
    14811463                self.doDelete(group.ident) 
    1482                  
     1464 
    14831465    def deleteManyBillingCodes(self, billingcodes) : 
    14841466        """Deletes many billing codes.""" 
    14851467        for bcode in billingcodes : 
    14861468            bcode.delete() 
    1487          
    1488     def deleteManyUsers(self, users) :         
     1469 
     1470    def deleteManyUsers(self, users) : 
    14891471        """Deletes many users.""" 
    14901472        for user in users : 
    14911473            user.delete() 
    1492              
    1493     def deleteManyGroups(self, groups) :         
     1474 
     1475    def deleteManyGroups(self, groups) : 
    14941476        """Deletes many groups.""" 
    14951477        for group in groups : 
    14961478            group.delete() 
    1497          
    1498     def deleteManyPrinters(self, printers) :         
     1479 
     1480    def deleteManyPrinters(self, printers) : 
    14991481        """Deletes many printers.""" 
    15001482        for printer in printers : 
    15011483            printer.delete() 
    1502          
    1503     def deleteManyUserPQuotas(self, printers, users) :         
     1484 
     1485    def deleteManyUserPQuotas(self, printers, users) : 
    15041486        """Deletes many user print quota entries.""" 
    15051487        # TODO : grab all with a single (possibly VERY huge) filter if possible (might depend on the LDAP server !) 
     
    15091491                if upq.Exists : 
    15101492                    upq.delete() 
    1511              
     1493 
    15121494    def deleteManyGroupPQuotas(self, printers, groups) : 
    15131495        """Deletes many group print quota entries.""" 
     
    15181500                if gpq.Exists : 
    15191501                    gpq.delete() 
    1520                  
    1521     def deleteUserPQuota(self, upquota) :     
     1502 
     1503    def deleteUserPQuota(self, upquota) : 
    15221504        """Completely deletes an user print quota entry from the database.""" 
    15231505        uname = self.userCharsetToDatabase(upquota.User.Name) 
     
    15311513            self.doDelete(upquota.Printer.LastJob.lastjobident) 
    15321514        self.doDelete(upquota.ident) 
    1533          
    1534     def deleteGroupPQuota(self, gpquota) :     
     1515 
     1516    def deleteGroupPQuota(self, gpquota) : 
    15351517        """Completely deletes a group print quota entry from the database.""" 
    15361518        self.doDelete(gpquota.ident) 
    1537                  
    1538     def deletePrinter(self, printer) :     
     1519 
     1520    def deletePrinter(self, printer) : 
    15391521        """Completely deletes a printer from the Quota Storage.""" 
    15401522        pname = self.userCharsetToDatabase(printer.Name) 
     
    15591541        for (ident, fields) in result : 
    15601542            self.doDelete(ident) 
    1561         for parent in self.getParentPrinters(printer) :   
     1543        for parent in self.getParentPrinters(printer) : 
    15621544            try : 
    15631545                parent.uniqueMember.remove(printer.ident) 
    1564             except ValueError :     
     1546            except ValueError : 
    15651547                pass 
    1566             else :     
     1548            else : 
    15671549                fields = { 
    15681550                           "uniqueMember" : parent.uniqueMember, 
    1569                          }   
    1570                 self.doModify(parent.ident, fields)          
    1571         self.doDelete(printer.ident)     
    1572          
     1551                         } 
     1552                self.doModify(parent.ident, fields) 
     1553        self.doDelete(printer.ident) 
     1554 
    15731555    def deleteBillingCode(self, code) : 
    15741556        """Deletes a billing code from the Quota Storage (no entries are deleted from the history)""" 
    15751557        self.doDelete(code.ident) 
    1576          
    1577     def sortRecords(self, fields, records, default, ordering) :      
     1558 
     1559    def sortRecords(self, fields, records, default, ordering) : 
    15781560        """Sort records based on list of fields prefixed with '+' (ASC) or '-' (DESC).""" 
    15791561        fieldindexes = {} 
    15801562        for i in range(len(fields)) : 
    15811563            fieldindexes[fields[i]] = i 
    1582         if not ordering :     
     1564        if not ordering : 
    15831565            ordering = default 
    1584         orderby = []     
     1566        orderby = [] 
    15851567        for orderkey in ordering : 
    15861568            # Create ordering hints, ignoring unknown fields 
     
    15931575                if index is not None : 
    15941576                    orderby.append((+1, index)) 
    1595             else :     
     1577            else : 
    15961578                index = fieldindexes.get(orderkey) 
    15971579                if index is not None : 
    15981580                    orderby.append((+1, index)) 
    1599                  
    1600         def compare(x, y, orderby=orderby) :     
     1581 
     1582        def compare(x, y, orderby=orderby) : 
    16011583            """Compares two records.""" 
    16021584            i = 0 
     
    16071589                if not result : 
    16081590                    i += 1 
    1609                 else :     
     1591                else : 
    16101592                    return sign * result 
    1611             return 0 # identical keys         
    1612              
     1593            return 0 # identical keys 
     1594 
    16131595        records.sort(compare) 
    16141596        return records 
    1615          
     1597 
    16161598    def extractPrinters(self, extractonly={}, ordering=[]) : 
    16171599        """Extracts all printer records.""" 
     
    16241606                if entry.PassThrough in (1, "1", "t", "true", "T", "TRUE", "True") : 
    16251607                    passthrough = "t" 
    1626                 else :     
     1608                else : 
    16271609                    passthrough = "f" 
    16281610                result.append((entry.ident, entry.Name, entry.PricePerPage, entry.PricePerJob, entry.Description, entry.MaxJobSize, passthrough)) 
    1629             return [fields] + self.sortRecords(fields, result, ["+dn"], ordering)  
    1630          
     1611            return [fields] + self.sortRecords(fields, result, ["+dn"], ordering) 
     1612 
    16311613    def extractUsers(self, extractonly={}, ordering=[]) : 
    16321614        """Extracts all user records.""" 
     
    16391621                result.append((entry.ident, entry.Name, entry.AccountBalance, entry.LifeTimePaid, entry.LimitBy, entry.Email, entry.Description, entry.OverCharge)) 
    16401622            return [fields] + self.sortRecords(fields, result, ["+dn"], ordering) 
    1641          
     1623 
    16421624    def extractBillingcodes(self, extractonly={}, ordering=[]) : 
    16431625        """Extracts all billing codes records.""" 
     
    16501632                result.append((entry.ident, entry.BillingCode, entry.Balance, entry.PageCounter, entry.Description)) 
    16511633            return [fields] + self.sortRecords(fields, result, ["+dn"], ordering) 
    1652          
     1634 
    16531635    def extractGroups(self, extractonly={}, ordering=[]) : 
    16541636        """Extracts all group records.""" 
     
    16611643                result.append((entry.ident, entry.Name, entry.LimitBy, entry.AccountBalance, entry.LifeTimePaid, entry.Description)) 
    16621644            return [fields] + self.sortRecords(fields, result, ["+dn"], ordering) 
    1663          
     1645 
    16641646    def extractPayments(self, extractonly={}, ordering=[]) : 
    16651647        """Extracts all payment records.""" 
     
    16801662                        result.append((entry.Name, amount, date, description)) 
    16811663            return [fields] + self.sortRecords(fields, result, ["+date"], ordering) 
    1682          
     1664 
    16831665    def extractUpquotas(self, extractonly={}, ordering=[]) : 
    16841666        """Extracts all userpquota records.""" 
     
    16931675                    result.append((user.Name, entry.Name, userpquota.ident, user.ident, entry.ident, userpquota.LifePageCounter, userpquota.PageCounter, userpquota.SoftLimit, userpquota.HardLimit, userpquota.DateLimit)) 
    16941676            return [fields] + self.sortRecords(fields, result, ["+userdn"], ordering) 
    1695          
     1677 
    16961678    def extractGpquotas(self, extractonly={}, ordering=[]) : 
    16971679        """Extracts all grouppquota records.""" 
     
    17061688                    result.append((group.Name, entry.Name, grouppquota.ident, group.ident, entry.ident, grouppquota.LifePageCounter, grouppquota.PageCounter, grouppquota.SoftLimit, grouppquota.HardLimit, grouppquota.DateLimit)) 
    17071689            return [fields] + self.sortRecords(fields, result, ["+groupdn"], ordering) 
    1708          
     1690 
    17091691    def extractUmembers(self, extractonly={}, ordering=[]) : 
    17101692        """Extracts all user groups members.""" 
     
    17201702                        result.append((entry.Name, member.Name, entry.ident, member.ident)) 
    17211703            return [fields] + self.sortRecords(fields, result, ["+groupdn", "+userdn"], ordering) 
    1722                  
     1704 
    17231705    def extractPmembers(self, extractonly={}, ordering=[]) : 
    17241706        """Extracts all printer groups members.""" 
     
    17341716                        result.append((parent.Name, entry.Name, parent.ident, entry.ident)) 
    17351717            return [fields] + self.sortRecords(fields, result, ["+pgroupdn", "+printerdn"], ordering) 
    1736          
     1718 
    17371719    def extractHistory(self, extractonly={}, ordering=[]) : 
    17381720        """Extracts all jobhistory records.""" 
     
    17401722        if uname : 
    17411723            user = self.getUser(uname) 
    1742         else :     
     1724        else : 
    17431725            user = None 
    17441726        pname = extractonly.get("printername") 
    17451727        if pname : 
    17461728            printer = self.getPrinter(pname) 
    1747         else :     
     1729        else : 
    17481730            printer = None 
    17491731        startdate = extractonly.get("start") 
     
    17551737            result = [] 
    17561738            for entry in entries : 
    1757                 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))  
     1739                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)) 
    17581740            return [fields] + self.sortRecords(fields, result, ["+dn"], ordering) 
    1759              
     1741 
    17601742    def getBillingCodeFromBackend(self, label) : 
    17611743        """Extracts billing code information given its label : returns first matching billing code.""" 
     
    17721754            code.PageCounter = int(fields.get("pykotaPageCounter", [0])[0]) 
    17731755            code.Balance = float(fields.get("pykotaBalance", [0.0])[0]) 
    1774             code.Description = self.databaseToUserCharset(fields.get("description", [""])[0])  
     1756            code.Description = self.databaseToUserCharset(fields.get("description", [""])[0]) 
    17751757            code.Exists = True 
    1776         return code     
    1777          
     1758        return code 
     1759 
    17781760    def addBillingCode(self, bcode) : 
    17791761        """Adds a billing code to the quota storage, returns it.""" 
     
    17881770                   "pykotaPageCounter" : str(bcode.PageCounter or 0), 
    17891771                   "pykotaBalance" : str(bcode.Balance or 0.0), 
    1790                    "description" : self.userCharsetToDatabase(bcode.Description or ""),  
    1791                  }  
     1772                   "description" : self.userCharsetToDatabase(bcode.Description or ""), 
     1773                 } 
    17921774        self.doAdd(dn, fields) 
    17931775        bcode.isDirty = False 
    17941776        return None # the entry created doesn't need further modification 
    1795          
     1777 
    17961778    def saveBillingCode(self, bcode) : 
    17971779        """Sets the new description for a billing code.""" 
    17981780        fields = { 
    1799                    "description" : self.userCharsetToDatabase(bcode.Description or ""),  
     1781                   "description" : self.userCharsetToDatabase(bcode.Description or ""), 
    18001782                   "pykotaPageCounter" : str(bcode.PageCounter or 0), 
    18011783                   "pykotaBalance" : str(bcode.Balance or 0.0), 
    18021784                 } 
    18031785        self.doModify(bcode.ident, fields) 
    1804              
     1786 
    18051787    def getMatchingBillingCodes(self, billingcodepattern) : 
    18061788        """Returns the list of all billing codes which match a certain pattern.""" 
     
    18111793        if result : 
    18121794            patterns = billingcodepattern.split(",") 
    1813             try : 
    1814                 patdict = {}.fromkeys(patterns) 
    1815             except AttributeError :     
    1816                 # Python v2.2 or earlier 
    1817                 patdict = {} 
    1818                 for p in patterns : 
    1819                     patdict[p] = None 
     1795            patdict = {}.fromkeys(patterns) 
    18201796            for (codeid, fields) in result : 
    18211797                codename = self.databaseToUserCharset(fields.get("pykotaBillingCode", [""])[0]) 
     
    18251801                    code.PageCounter = int(fields.get("pykotaPageCounter", [0])[0]) 
    18261802                    code.Balance = float(fields.get("pykotaBalance", [0.0])[0]) 
    1827                     code.Description = self.databaseToUserCharset(fields.get("description", [""])[0])  
     1803                    code.Description = self.databaseToUserCharset(fields.get("description", [""])[0]) 
    18281804                    code.Exists = True 
    18291805                    codes.append(code) 
    18301806                    self.cacheEntry("BILLINGCODES", code.BillingCode, code) 
    1831         return codes         
    1832          
     1807        return codes 
     1808 
    18331809    def consumeBillingCode(self, bcode, pagecounter, balance) : 
    18341810        """Consumes from a billing code.""" 
     
    18371813                   "pykotaPageCounter" : { "operator" : "+", "value" : pagecounter, "convert" : int }, 
    18381814                 } 
    1839         return self.doModify(bcode.ident, fields)          
    1840  
    1841     def refundJob(self, jobident) :    
     1815        return self.doModify(bcode.ident, fields) 
     1816 
     1817    def refundJob(self, jobident) : 
    18421818        """Marks a job as refunded in the history.""" 
    18431819        dn = "cn=%s,%s" % (ident, self.info["jobbase"]) 
    18441820        fields = { 
    18451821                     "pykotaAction" : "REFUND", 
    1846                  }     
    1847         self.doModify(dn, fields)          
    1848          
     1822                 } 
     1823        self.doModify(dn, fields) 
     1824 
    18491825    def storageUserFromRecord(self, username, record) : 
    18501826        """Returns a StorageUser instance from a database record.""" 
     
    18521828        user.Exists = True 
    18531829        return user 
    1854          
     1830 
    18551831    def storageGroupFromRecord(self, groupname, record) : 
    18561832        """Returns a StorageGroup instance from a database record.""" 
     
    18581834        group.Exists = True 
    18591835        return group 
    1860          
     1836 
    18611837    def storagePrinterFromRecord(self, printername, record) : 
    18621838        """Returns a StoragePrinter instance from a database record.""" 
     
    18641840        printer.Exists = True 
    18651841        return printer 
    1866          
    1867     def setJobAttributesFromRecord(self, job, record) :     
     1842 
     1843    def setJobAttributesFromRecord(self, job, record) : 
    18681844        """Sets the attributes of a job from a database record.""" 
    18691845        job.Exists = True 
    1870          
     1846 
    18711847    def storageJobFromRecord(self, record) : 
    18721848        """Returns a StorageJob instance from a database record.""" 
     
    18741850        self.setJobAttributesFromRecord(job, record) 
    18751851        return job 
    1876          
     1852 
    18771853    def storageLastJobFromRecord(self, printer, record) : 
    18781854        """Returns a StorageLastJob instance from a database record.""" 
     
    18801856        self.setJobAttributesFromRecord(lastjob, record) 
    18811857        return lastjob 
    1882          
     1858 
    18831859    def storageUserPQuotaFromRecord(self, user, printer, record) : 
    18841860        """Returns a StorageUserPQuota instance from a database record.""" 
     
    18861862        userpquota.Exists = True 
    18871863        return userpquota 
    1888          
     1864 
    18891865    def storageGroupPQuotaFromRecord(self, group, printer, record) : 
    18901866        """Returns a StorageGroupPQuota instance from a database record.""" 
     
    18921868        grouppquota.Exists = True 
    18931869        return grouppquota 
    1894          
     1870 
    18951871    def storageBillingCodeFromRecord(self, billingcode, record) : 
    18961872        """Returns a StorageBillingCode instance from a database record.""" 
  • pykota/branches/1.26_fixes/pykota/storages/sql.py

    r3393 r3522  
    1414# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
    1515# GNU General Public License for more details. 
    16 #  
     16# 
    1717# You should have received a copy of the GNU General Public License 
    1818# along with this program; if not, write to the Free Software 
     
    2828                           StorageJob, StorageLastJob, StorageUserPQuota, \ 
    2929                           StorageGroupPQuota, StorageBillingCode 
     30 
     31MAXINNAMES = 500 # Maximum number of non-patterns names to use in a single IN statement 
    3032 
    3133class SQLStorage : 
     
    4244        user.Exists = True 
    4345        return user 
    44          
     46 
    4547    def storageGroupFromRecord(self, groupname, record) : 
    4648        """Returns a StorageGroup instance from a database record.""" 
     
    5355        group.Exists = True 
    5456        return group 
    55          
     57 
    5658    def storagePrinterFromRecord(self, printername, record) : 
    5759        """Returns a StoragePrinter instance from a database record.""" 
     
    6971        printer.Exists = True 
    7072        return printer 
    71          
    72     def setJobAttributesFromRecord(self, job, record) :     
     73 
     74    def setJobAttributesFromRecord(self, job, record) : 
    7375        """Sets the attributes of a job from a database record.""" 
    7476        job.ident = record.get("id") 
     
    7880        job.JobPrice = record.get("jobprice") 
    7981        job.JobAction = record.get("action") 
    80         job.JobFileName = self.databaseToUserCharset(record.get("filename") or "")  
    81         job.JobTitle = self.databaseToUserCharset(record.get("title") or "")  
     82        job.JobFileName = self.databaseToUserCharset(record.get("filename") or "") 
     83        job.JobTitle = self.databaseToUserCharset(record.get("title") or "") 
    8284        job.JobCopies = record.get("copies") 
    83         job.JobOptions = self.databaseToUserCharset(record.get("options") or "")  
     85        job.JobOptions = self.databaseToUserCharset(record.get("options") or "") 
    8486        job.JobDate = record.get("jobdate") 
    8587        job.JobHostName = record.get("hostname") 
     
    9597            (job.JobTitle, job.JobFileName, job.JobOptions) = (_("Hidden because of privacy concerns"),) * 3 
    9698        job.Exists = True 
    97          
     99 
    98100    def storageJobFromRecord(self, record) : 
    99101        """Returns a StorageJob instance from a database record.""" 
     
    101103        self.setJobAttributesFromRecord(job, record) 
    102104        return job 
    103          
     105 
    104106    def storageLastJobFromRecord(self, printer, record) : 
    105107        """Returns a StorageLastJob instance from a database record.""" 
     
    107109        self.setJobAttributesFromRecord(lastjob, record) 
    108110        return lastjob 
    109          
     111 
    110112    def storageUserPQuotaFromRecord(self, user, printer, record) : 
    111113        """Returns a StorageUserPQuota instance from a database record.""" 
     
    120122        userpquota.Exists = True 
    121123        return userpquota 
    122          
     124 
    123125    def storageGroupPQuotaFromRecord(self, group, printer, record) : 
    124126        """Returns a StorageGroupPQuota instance from a database record.""" 
     
    135137        grouppquota.Exists = True 
    136138        return grouppquota 
    137          
     139 
    138140    def storageBillingCodeFromRecord(self, billingcode, record) : 
    139141        """Returns a StorageBillingCode instance from a database record.""" 
     
    145147        code.Exists = True 
    146148        return code 
    147          
    148     def createFilter(self, only) :     
     149 
     150    def createFilter(self, only) : 
    149151        """Returns the appropriate SQL filter.""" 
    150152        if only : 
     
    152154            for (k, v) in only.items() : 
    153155                expressions.append("%s=%s" % (k, self.doQuote(self.userCharsetToDatabase(v)))) 
    154             return " AND ".join(expressions)      
    155         return ""         
    156          
    157     def createOrderBy(self, default, ordering) :     
     156            return " AND ".join(expressions) 
     157        return "" 
     158 
     159    def createOrderBy(self, default, ordering) : 
    158160        """Creates a suitable ORDER BY statement based on a list of fieldnames prefixed with '+' (ASC) or '-' (DESC).""" 
    159161        statements = [] 
    160162        if not ordering : 
    161163            ordering = default 
    162         for field in ordering :     
    163             if field.startswith("-") :     
     164        for field in ordering : 
     165            if field.startswith("-") : 
    164166                statements.append("%s DESC" % field[1:]) 
    165167            elif field.startswith("+") : 
    166168                statements.append("%s ASC" % field[1:]) 
    167             else :     
     169            else : 
    168170                statements.append("%s ASC" % field) 
    169         return ", ".join(statements)     
    170          
     171        return ", ".join(statements) 
     172 
    171173    def extractPrinters(self, extractonly={}, ordering=[]) : 
    172174        """Extracts all printer records.""" 
     
    177179        result = self.doRawSearch("SELECT * FROM printers %(thefilter)s ORDER BY %(orderby)s" % locals()) 
    178180        return self.prepareRawResult(result) 
    179          
     181 
    180182    def extractUsers(self, extractonly={}, ordering=[]) : 
    181183        """Extracts all user records.""" 
     
    186188        result = self.doRawSearch("SELECT * FROM users %(thefilter)s ORDER BY %(orderby)s" % locals()) 
    187189        return self.prepareRawResult(result) 
    188          
     190 
    189191    def extractBillingcodes(self, extractonly={}, ordering=[]) : 
    190192        """Extracts all billing codes records.""" 
     
    195197        result = self.doRawSearch("SELECT * FROM billingcodes %(thefilter)s ORDER BY %(orderby)s" % locals()) 
    196198        return self.prepareRawResult(result) 
    197          
     199 
    198200    def extractGroups(self, extractonly={}, ordering=[]) : 
    199201        """Extracts all group records.""" 
     
    204206        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()) 
    205207        return self.prepareRawResult(result) 
    206          
     208 
    207209    def extractPayments(self, extractonly={}, ordering=[]) : 
    208210        """Extracts all payment records.""" 
     
    212214            try : 
    213215                del extractonly[limit] 
    214             except KeyError :     
     216            except KeyError : 
    215217                pass 
    216218        thefilter = self.createFilter(extractonly) 
     
    218220            thefilter = "AND %s" % thefilter 
    219221        (startdate, enddate) = self.cleanDates(startdate, enddate) 
    220         if startdate :  
     222        if startdate : 
    221223            thefilter = "%s AND date>=%s" % (thefilter, self.doQuote(startdate)) 
    222         if enddate :  
     224        if enddate : 
    223225            thefilter = "%s AND date<=%s" % (thefilter, self.doQuote(enddate)) 
    224226        orderby = self.createOrderBy(["+payments.id"], ordering) 
    225227        result = self.doRawSearch("SELECT username,payments.* FROM users,payments WHERE users.id=payments.userid %(thefilter)s ORDER BY %(orderby)s" % locals()) 
    226228        return self.prepareRawResult(result) 
    227          
     229 
    228230    def extractUpquotas(self, extractonly={}, ordering=[]) : 
    229231        """Extracts all userpquota records.""" 
     
    234236        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()) 
    235237        return self.prepareRawResult(result) 
    236          
     238 
    237239    def extractGpquotas(self, extractonly={}, ordering=[]) : 
    238240        """Extracts all grouppquota records.""" 
     
    243245        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()) 
    244246        return self.prepareRawResult(result) 
    245          
     247 
    246248    def extractUmembers(self, extractonly={}, ordering=[]) : 
    247249        """Extracts all user groups members.""" 
     
    252254        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()) 
    253255        return self.prepareRawResult(result) 
    254          
     256 
    255257    def extractPmembers(self, extractonly={}, ordering=[]) : 
    256258        """Extracts all printer groups members.""" 
     
    268270        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()) 
    269271        return self.prepareRawResult(result) 
    270          
     272 
    271273    def extractHistory(self, extractonly={}, ordering=[]) : 
    272274        """Extracts all jobhistory records.""" 
     
    276278            try : 
    277279                del extractonly[limit] 
    278             except KeyError :     
     280            except KeyError : 
    279281                pass 
    280282        thefilter = self.createFilter(extractonly) 
     
    282284            thefilter = "AND %s" % thefilter 
    283285        (startdate, enddate) = self.cleanDates(startdate, enddate) 
    284         if startdate :  
     286        if startdate : 
    285287            thefilter = "%s AND jobdate>=%s" % (thefilter, self.doQuote(startdate)) 
    286         if enddate :  
     288        if enddate : 
    287289            thefilter = "%s AND jobdate<=%s" % (thefilter, self.doQuote(enddate)) 
    288290        orderby = self.createOrderBy(["+jobhistory.id"], ordering) 
    289291        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()) 
    290292        return self.prepareRawResult(result) 
    291              
     293 
    292294    def filterNames(self, records, attribute, patterns=None) : 
    293295        """Returns a list of 'attribute' from a list of records. 
    294          
     296 
    295297           Logs any missing attribute. 
    296         """    
     298        """ 
    297299        result = [] 
    298300        for record in records : 
     
    307309                    if self.tool.matchString(attrval, patterns) : 
    308310                        result.append(attrval) 
    309                 else :     
     311                else : 
    310312                    result.append(attrval) 
    311         return result    
    312                  
    313     def getAllBillingCodes(self, billingcode=None) :     
     313        return result 
     314 
     315    def getAllBillingCodes(self, billingcode=None) : 
    314316        """Extracts all billing codes or only the billing codes matching the optional parameter.""" 
    315317        result = self.doSearch("SELECT billingcode FROM billingcodes") 
    316318        if result : 
    317319            return self.filterNames(result, "billingcode", billingcode) 
    318         else :     
     320        else : 
    319321            return [] 
    320          
    321     def getAllPrintersNames(self, printername=None) :     
     322 
     323    def getAllPrintersNames(self, printername=None) : 
    322324        """Extracts all printer names or only the printers' names matching the optional parameter.""" 
    323325        result = self.doSearch("SELECT printername FROM printers") 
    324326        if result : 
    325327            return self.filterNames(result, "printername", printername) 
    326         else :     
     328        else : 
    327329            return [] 
    328      
    329     def getAllUsersNames(self, username=None) :     
     330 
     331    def getAllUsersNames(self, username=None) : 
    330332        """Extracts all user names.""" 
    331333        result = self.doSearch("SELECT username FROM users") 
    332334        if result : 
    333335            return self.filterNames(result, "username", username) 
    334         else :     
     336        else : 
    335337            return [] 
    336          
    337     def getAllGroupsNames(self, groupname=None) :     
     338 
     339    def getAllGroupsNames(self, groupname=None) : 
    338340        """Extracts all group names.""" 
    339341        result = self.doSearch("SELECT groupname FROM groups") 
     
    342344        else : 
    343345            return [] 
    344          
     346 
    345347    def getUserNbJobsFromHistory(self, user) : 
    346348        """Returns the number of jobs the user has in history.""" 
     
    349351            return result[0]["count"] 
    350352        return 0 
    351          
    352     def getUserFromBackend(self, username) :     
     353 
     354    def getUserFromBackend(self, username) : 
    353355        """Extracts user information given its name.""" 
    354356        result = self.doSearch("SELECT * FROM users WHERE username=%s LIMIT 1"\ 
     
    356358        if result : 
    357359            return self.storageUserFromRecord(username, result[0]) 
    358         else :     
     360        else : 
    359361            return StorageUser(self, username) 
    360         
    361     def getGroupFromBackend(self, groupname) :     
     362 
     363    def getGroupFromBackend(self, groupname) : 
    362364        """Extracts group information given its name.""" 
    363365        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" \ 
     
    365367        if result : 
    366368            return self.storageGroupFromRecord(groupname, result[0]) 
    367         else :     
     369        else : 
    368370            return StorageGroup(self, groupname) 
    369         
    370     def getPrinterFromBackend(self, printername) :         
     371 
     372    def getPrinterFromBackend(self, printername) : 
    371373        """Extracts printer information given its name.""" 
    372374        result = self.doSearch("SELECT * FROM printers WHERE printername=%s LIMIT 1" \ 
     
    374376        if result : 
    375377            return self.storagePrinterFromRecord(printername, result[0]) 
    376         else :     
     378        else : 
    377379            return StoragePrinter(self, printername) 
    378          
    379     def getBillingCodeFromBackend(self, label) :         
     380 
     381    def getBillingCodeFromBackend(self, label) : 
    380382        """Extracts a billing code information given its name.""" 
    381383        result = self.doSearch("SELECT * FROM billingcodes WHERE billingcode=%s LIMIT 1" \ 
     
    383385        if result : 
    384386            return self.storageBillingCodeFromRecord(label, result[0]) 
    385         else :     
     387        else : 
    386388            return StorageBillingCode(self, label) 
    387          
    388     def getUserPQuotaFromBackend(self, user, printer) :         
     389 
     390    def getUserPQuotaFromBackend(self, user, printer) : 
    389391        """Extracts a user print quota.""" 
    390392        if printer.Exists and user.Exists : 
     
    394396                return self.storageUserPQuotaFromRecord(user, printer, result[0]) 
    395397        return StorageUserPQuota(self, user, printer) 
    396          
    397     def getGroupPQuotaFromBackend(self, group, printer) :         
     398 
     399    def getGroupPQuotaFromBackend(self, group, printer) : 
    398400        """Extracts a group print quota.""" 
    399401        if printer.Exists and group.Exists : 
     
    403405                return self.storageGroupPQuotaFromRecord(group, printer, result[0]) 
    404406        return StorageGroupPQuota(self, group, printer) 
    405          
    406     def getPrinterLastJobFromBackend(self, printer) :         
     407 
     408    def getPrinterLastJobFromBackend(self, printer) : 
    407409        """Extracts a printer's last job information.""" 
    408410        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)) 
    409411        if result : 
    410412            return self.storageLastJobFromRecord(printer, result[0]) 
    411         else :     
     413        else : 
    412414            return StorageLastJob(self, printer) 
    413              
    414     def getGroupMembersFromBackend(self, group) :         
     415 
     416    def getGroupMembersFromBackend(self, group) : 
    415417        """Returns the group's members list.""" 
    416418        groupmembers = [] 
     
    422424                groupmembers.append(user) 
    423425                self.cacheEntry("USERS", user.Name, user) 
    424         return groupmembers         
    425          
    426     def getUserGroupsFromBackend(self, user) :         
     426        return groupmembers 
     427 
     428    def getUserGroupsFromBackend(self, user) : 
    427429        """Returns the user's groups list.""" 
    428430        groups = [] 
     
    431433            for record in result : 
    432434                groups.append(self.getGroup(self.databaseToUserCharset(record.get("groupname")))) 
    433         return groups         
    434          
    435     def getParentPrintersFromBackend(self, printer) :     
     435        return groups 
     436 
     437    def getParentPrintersFromBackend(self, printer) : 
    436438        """Get all the printer groups this printer is a member of.""" 
    437439        pgroups = [] 
     
    444446                        pgroups.append(parentprinter) 
    445447        return pgroups 
    446          
     448 
     449    def hasWildCards(self, pattern) : 
     450        """Returns True if the pattern contains wildcards, else False.""" 
     451        specialchars = "*?[!" # no need to check for ] since [ would be there first 
     452        for specialchar in specialchars : 
     453            if specialchar in pattern : 
     454                return True 
     455        return False 
     456 
    447457    def getMatchingPrinters(self, printerpattern) : 
    448458        """Returns the list of all printers for which name matches a certain pattern.""" 
     
    454464        if result : 
    455465            patterns = printerpattern.split(",") 
    456             try : 
    457                 patdict = {}.fromkeys(patterns) 
    458             except AttributeError :     
    459                 # Python v2.2 or earlier 
    460                 patdict = {} 
    461                 for p in patterns : 
    462                     patdict[p] = None 
     466            patdict = {}.fromkeys(patterns) 
    463467            for record in result : 
    464468                pname = self.databaseToUserCharset(record["printername"]) 
     
    467471                    printers.append(printer) 
    468472                    self.cacheEntry("PRINTERS", printer.Name, printer) 
    469         return printers         
    470          
     473        return printers 
     474 
    471475    def getMatchingUsers(self, userpattern) : 
    472476        """Returns the list of all users for which name matches a certain pattern.""" 
     
    475479        # but we don't because other storages semantics may be different, so every 
    476480        # storage should use fnmatch to match patterns and be storage agnostic 
    477         result = self.doSearch("SELECT * FROM users") 
    478         if result : 
    479             patterns = userpattern.split(",") 
    480             try : 
    481                 patdict = {}.fromkeys(patterns) 
    482             except AttributeError :     
    483                 # Python v2.2 or earlier 
    484                 patdict = {} 
    485                 for p in patterns : 
    486                     patdict[p] = None 
    487             for record in result : 
    488                 uname = self.databaseToUserCharset(record["username"]) 
    489                 if patdict.has_key(uname) or self.tool.matchString(uname, patterns) : 
    490                     user = self.storageUserFromRecord(uname, record) 
    491                     users.append(user) 
    492                     self.cacheEntry("USERS", user.Name, user) 
    493         return users         
    494          
     481        # 
     482        # This doesn't prevent us from being smarter, thanks to bse@chalmers.se 
     483        userpattern = userpattern or "*" 
     484        patterns = userpattern.split(",") 
     485        patdict = {}.fromkeys(patterns) 
     486        patterns = patdict.keys() # Ensures the uniqueness of each pattern, but we lose the cmd line ordering 
     487        # BEWARE : if a single pattern contains wild cards, we'll still use the slow route. 
     488        if self.hasWildCards(userpattern) : 
     489            # Slow route 
     490            result = self.doSearch("SELECT * FROM users") 
     491            if result : 
     492                for record in result : 
     493                    uname = self.databaseToUserCharset(record["username"]) 
     494                    if patdict.has_key(uname) or self.tool.matchString(uname, patterns) : 
     495                        user = self.storageUserFromRecord(uname, record) 
     496                        users.append(user) 
     497                        self.cacheEntry("USERS", user.Name, user) 
     498        else : 
     499            # Fast route (probably not faster with a few users) 
     500            while patterns : 
     501                subset = patterns[:MAXINNAMES] 
     502                nbpatterns = len(subset) 
     503                if nbpatterns == 1 : 
     504                    wherestmt = "username=%s" % self.doQuote(self.userCharsetToDatabase(subset[0])) 
     505                else : 
     506                    wherestmt = "username IN (%s)" % ",".join([self.doQuote(self.userCharsetToDatabase(p)) for p in subset]) 
     507                result = self.doSearch("SELECT * FROM users WHERE %s" % wherestmt) 
     508                if result : 
     509                    for record in result : 
     510                        uname = self.databaseToUserCharset(record["username"]) 
     511                        user = self.storageUserFromRecord(uname, record) 
     512                        users.append(user) 
     513                        self.cacheEntry("USERS", user.Name, user) 
     514                patterns = patterns[MAXINNAMES:] 
     515        users.sort(key=lambda u : u.Name) # Adds some ordering, we've already lost the cmd line one anyway. 
     516        return users 
     517 
    495518    def getMatchingGroups(self, grouppattern) : 
    496519        """Returns the list of all groups for which name matches a certain pattern.""" 
     
    502525        if result : 
    503526            patterns = grouppattern.split(",") 
    504             try : 
    505                 patdict = {}.fromkeys(patterns) 
    506             except AttributeError :     
    507                 # Python v2.2 or earlier 
    508                 patdict = {} 
    509                 for p in patterns : 
    510                     patdict[p] = None 
     527            patdict = {}.fromkeys(patterns) 
    511528            for record in result : 
    512529                gname = self.databaseToUserCharset(record["groupname"]) 
     
    515532                    groups.append(group) 
    516533                    self.cacheEntry("GROUPS", group.Name, group) 
    517         return groups         
    518          
     534        return groups 
     535 
    519536    def getMatchingBillingCodes(self, billingcodepattern) : 
    520537        """Returns the list of all billing codes for which the label matches a certain pattern.""" 
     
    523540        if result : 
    524541            patterns = billingcodepattern.split(",") 
    525             try : 
    526                 patdict = {}.fromkeys(patterns) 
    527             except AttributeError :     
    528                 # Python v2.2 or earlier 
    529                 patdict = {} 
    530                 for p in patterns : 
    531                     patdict[p] = None 
     542            patdict = {}.fromkeys(patterns) 
    532543            for record in result : 
    533544                codename = self.databaseToUserCharset(record["billingcode"]) 
     
    536547                    codes.append(code) 
    537548                    self.cacheEntry("BILLINGCODES", code.BillingCode, code) 
    538         return codes         
    539          
    540     def getPrinterUsersAndQuotas(self, printer, names=["*"]) :         
     549        return codes 
     550 
     551    def getPrinterUsersAndQuotas(self, printer, names=["*"]) : 
    541552        """Returns the list of users who uses a given printer, along with their quotas.""" 
    542553        usersandquotas = [] 
     
    552563                    self.cacheEntry("USERPQUOTAS", "%s@%s" % (user.Name, printer.Name), userpquota) 
    553564        return usersandquotas 
    554                  
    555     def getPrinterGroupsAndQuotas(self, printer, names=["*"]) :         
     565 
     566    def getPrinterGroupsAndQuotas(self, printer, names=["*"]) : 
    556567        """Returns the list of groups which uses a given printer, along with their quotas.""" 
    557568        groupsandquotas = [] 
     
    565576                    groupsandquotas.append((group, grouppquota)) 
    566577        return groupsandquotas 
    567          
    568     def addPrinter(self, printer) :         
     578 
     579    def addPrinter(self, printer) : 
    569580        """Adds a printer to the quota storage, returns the old value if it already exists.""" 
    570581        oldentry = self.getPrinter(printer.Name) 
     
    580591        printer.isDirty = False 
    581592        return None # the entry created doesn't need further modification 
    582          
     593 
    583594    def addBillingCode(self, bcode) : 
    584595        """Adds a billing code to the quota storage, returns the old value if it already exists.""" 
     
    587598            return oldentry 
    588599        self.doModify("INSERT INTO billingcodes (billingcode, balance, pagecounter, description) VALUES (%s, %s, %s, %s)" \ 
    589                            % (self.doQuote(self.userCharsetToDatabase(bcode.BillingCode)),  
     600                           % (self.doQuote(self.userCharsetToDatabase(bcode.BillingCode)), 
    590601                              self.doQuote(bcode.Balance or 0.0), \ 
    591602                              self.doQuote(bcode.PageCounter or 0), \ 
     
    593604        bcode.isDirty = False 
    594605        return None # the entry created doesn't need further modification 
    595          
    596     def addUser(self, user) :         
     606 
     607    def addUser(self, user) : 
    597608        """Adds a user to the quota storage, returns the old value if it already exists.""" 
    598609        oldentry = self.getUser(user.Name) 
     
    613624        user.isDirty = False 
    614625        return None # the entry created doesn't need further modification 
    615          
    616     def addGroup(self, group) :         
     626 
     627    def addGroup(self, group) : 
    617628        """Adds a group to the quota storage, returns the old value if it already exists.""" 
    618629        oldentry = self.getGroup(group.Name) 
     
    626637        return None # the entry created doesn't need further modification 
    627638 
    628     def addUserToGroup(self, user, group) :     
     639    def addUserToGroup(self, user, group) : 
    629640        """Adds an user to a group.""" 
    630641        result = self.doSearch("SELECT COUNT(*) AS mexists FROM groupsmembers WHERE groupid=%s AND userid=%s" % (self.doQuote(group.ident), self.doQuote(user.ident))) 
    631642        try : 
    632643            mexists = int(result[0].get("mexists")) 
    633         except (IndexError, TypeError) :     
     644        except (IndexError, TypeError) : 
    634645            mexists = 0 
    635         if not mexists :     
     646        if not mexists : 
    636647            self.doModify("INSERT INTO groupsmembers (groupid, userid) VALUES (%s, %s)" % (self.doQuote(group.ident), self.doQuote(user.ident))) 
    637              
    638     def delUserFromGroup(self, user, group) :     
     648 
     649    def delUserFromGroup(self, user, group) : 
    639650        """Removes an user from a group.""" 
    640651        self.doModify("DELETE FROM groupsmembers WHERE groupid=%s AND userid=%s" % \ 
    641652                       (self.doQuote(group.ident), self.doQuote(user.ident))) 
    642              
     653 
    643654    def addUserPQuota(self, upq) : 
    644655        """Initializes a user print quota on a printer.""" 
     
    658669        upq.isDirty = False 
    659670        return None # the entry created doesn't need further modification 
    660          
     671 
    661672    def addGroupPQuota(self, gpq) : 
    662673        """Initializes a group print quota on a printer.""" 
     
    673684        gpq.isDirty = False 
    674685        return None # the entry created doesn't need further modification 
    675          
    676     def savePrinter(self, printer) :     
     686 
     687    def savePrinter(self, printer) : 
    677688        """Saves the printer to the database in a single operation.""" 
    678689        self.doModify("UPDATE printers SET passthrough=%s, maxjobsize=%s, description=%s, priceperpage=%s, priceperjob=%s WHERE id=%s" \ 
     
    683694                                 self.doQuote(printer.PricePerJob or 0.0), \ 
    684695                                 self.doQuote(printer.ident))) 
    685                                   
    686     def saveUser(self, user) :         
     696 
     697    def saveUser(self, user) : 
    687698        """Saves the user to the database in a single operation.""" 
    688699        self.doModify("UPDATE users SET limitby=%s, balance=%s, lifetimepaid=%s, email=%s, overcharge=%s, description=%s WHERE id=%s" \ 
     
    694705                                  self.doQuote(self.userCharsetToDatabase(user.Description)), \ 
    695706                                  self.doQuote(user.ident))) 
    696                                    
    697     def saveGroup(self, group) :         
     707 
     708    def saveGroup(self, group) : 
    698709        """Saves the group to the database in a single operation.""" 
    699710        self.doModify("UPDATE groups SET limitby=%s, description=%s WHERE id=%s" \ 
     
    701712                                  self.doQuote(self.userCharsetToDatabase(group.Description)), \ 
    702713                                  self.doQuote(group.ident))) 
    703          
    704     def writeUserPQuotaDateLimit(self, userpquota, datelimit) :     
     714 
     715    def writeUserPQuotaDateLimit(self, userpquota, datelimit) : 
    705716        """Sets the date limit permanently for a user print quota.""" 
    706717        self.doModify("UPDATE userpquota SET datelimit=%s WHERE id=%s" % (self.doQuote(datelimit), self.doQuote(userpquota.ident))) 
    707              
    708     def writeGroupPQuotaDateLimit(self, grouppquota, datelimit) :     
     718 
     719    def writeGroupPQuotaDateLimit(self, grouppquota, datelimit) : 
    709720        """Sets the date limit permanently for a group print quota.""" 
    710721        self.doModify("UPDATE grouppquota SET datelimit=%s WHERE id=%s" % (self.doQuote(datelimit), self.doQuote(grouppquota.ident))) 
    711          
    712     def increaseUserPQuotaPagesCounters(self, userpquota, nbpages) :     
     722 
     723    def increaseUserPQuotaPagesCounters(self, userpquota, nbpages) : 
    713724        """Increase page counters for a user print quota.""" 
    714725        self.doModify("UPDATE userpquota SET pagecounter=pagecounter + %s,lifepagecounter=lifepagecounter + %s WHERE id=%s" % (self.doQuote(nbpages), self.doQuote(nbpages), self.doQuote(userpquota.ident))) 
    715         
    716     def saveBillingCode(self, bcode) :     
     726 
     727    def saveBillingCode(self, bcode) : 
    717728        """Saves the billing code to the database.""" 
    718729        self.doModify("UPDATE billingcodes SET balance=%s, pagecounter=%s, description=%s WHERE id=%s" \ 
     
    721732                               self.doQuote(self.userCharsetToDatabase(bcode.Description)), \ 
    722733                               self.doQuote(bcode.ident))) 
    723         
     734 
    724735    def consumeBillingCode(self, bcode, pagecounter, balance) : 
    725736        """Consumes from a billing code.""" 
    726737        self.doModify("UPDATE billingcodes SET balance=balance + %s, pagecounter=pagecounter + %s WHERE id=%s" % (self.doQuote(balance), self.doQuote(pagecounter), self.doQuote(bcode.ident))) 
    727         
    728     def refundJob(self, jobident) :    
     738 
     739    def refundJob(self, jobident) : 
    729740        """Marks a job as refunded in the history.""" 
    730741        self.doModify("UPDATE jobhistory SET action='REFUND' WHERE id=%s;" % self.doQuote(jobident)) 
    731          
    732     def decreaseUserAccountBalance(self, user, amount) :     
     742 
     743    def decreaseUserAccountBalance(self, user, amount) : 
    733744        """Decreases user's account balance from an amount.""" 
    734745        self.doModify("UPDATE users SET balance=balance - %s WHERE id=%s" % (self.doQuote(amount), self.doQuote(user.ident))) 
    735         
     746 
    736747    def writeNewPayment(self, user, amount, comment="") : 
    737748        """Adds a new payment to the payments history.""" 
    738749        if user.ident is not None : 
    739750            self.doModify("INSERT INTO payments (userid, amount, description) VALUES (%s, %s, %s)" % (self.doQuote(user.ident), self.doQuote(amount), self.doQuote(self.userCharsetToDatabase(comment)))) 
    740         else :     
     751        else : 
    741752            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)))) 
    742          
    743     def writeLastJobSize(self, lastjob, jobsize, jobprice) :         
     753 
     754    def writeLastJobSize(self, lastjob, jobsize, jobprice) : 
    744755        """Sets the last job's size permanently.""" 
    745756        self.doModify("UPDATE jobhistory SET jobsize=%s, jobprice=%s WHERE id=%s" % (self.doQuote(jobsize), self.doQuote(jobprice), self.doQuote(lastjob.ident))) 
    746          
     757 
    747758    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) : 
    748759        """Adds a job in a printer's history.""" 
    749         if self.privacy :     
     760        if self.privacy : 
    750761            # For legal reasons, we want to hide the title, filename and options 
    751762            title = filename = options = "hidden" 
     
    757768            if jobsize is not None : 
    758769                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))) 
    759             else :     
     770            else : 
    760771                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))) 
    761         else :         
     772        else : 
    762773            # here we explicitly want to reset jobsize to NULL if needed 
    763774            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))) 
    764              
     775 
    765776    def saveUserPQuota(self, userpquota) : 
    766777        """Saves an user print quota entry.""" 
     
    774785                                 self.doQuote(userpquota.MaxJobSize), \ 
    775786                                 self.doQuote(userpquota.ident))) 
    776          
     787 
    777788    def writeUserPQuotaWarnCount(self, userpquota, warncount) : 
    778789        """Sets the warn counter value for a user quota.""" 
    779790        self.doModify("UPDATE userpquota SET warncount=%s WHERE id=%s" % (self.doQuote(warncount), self.doQuote(userpquota.ident))) 
    780          
     791 
    781792    def increaseUserPQuotaWarnCount(self, userpquota) : 
    782793        """Increases the warn counter value for a user quota.""" 
    783794        self.doModify("UPDATE userpquota SET warncount=warncount+1 WHERE id=%s" % self.doQuote(userpquota.ident)) 
    784          
     795 
    785796    def saveGroupPQuota(self, grouppquota) : 
    786797        """Saves a group print quota entry.""" 
     
    798809            for record in result : 
    799810                children.append(record.get("printerid")) # TODO : put this into the database integrity rules 
    800         if printer.ident not in children :         
     811        if printer.ident not in children : 
    801812            self.doModify("INSERT INTO printergroupsmembers (groupid, printerid) VALUES (%s, %s)" % (self.doQuote(pgroup.ident), self.doQuote(printer.ident))) 
    802          
     813 
    803814    def removePrinterFromGroup(self, pgroup, printer) : 
    804815        """Removes a printer from a printer group.""" 
    805816        self.doModify("DELETE FROM printergroupsmembers WHERE groupid=%s AND printerid=%s" % (self.doQuote(pgroup.ident), self.doQuote(printer.ident))) 
    806          
     817 
    807818    def retrieveHistory(self, user=None, printer=None, hostname=None, billingcode=None, jobid=None, limit=100, start=None, end=None) : 
    808819        """Retrieves all print jobs for user on printer (or all) between start and end date, limited to first 100 results.""" 
     
    813824        if printer is not None : # printer.ident is None anyway if printer doesn't exist 
    814825            where.append("printerid=%s" % self.doQuote(printer.ident)) 
    815         if hostname is not None :     
     826        if hostname is not None : 
    816827            where.append("hostname=%s" % self.doQuote(hostname)) 
    817         if billingcode is not None :     
     828        if billingcode is not None : 
    818829            where.append("billingcode=%s" % self.doQuote(self.userCharsetToDatabase(billingcode))) 
    819         if jobid is not None :     
     830        if jobid is not None : 
    820831            where.append("jobid=%s" % self.doQuote(jobid)) # TODO : jobid is text, so self.userCharsetToDatabase(jobid) but do all of them as well. 
    821         if start is not None :     
     832        if start is not None : 
    822833            where.append("jobdate>=%s" % self.doQuote(start)) 
    823         if end is not None :     
     834        if end is not None : 
    824835            where.append("jobdate<=%s" % self.doQuote(end)) 
    825         if where :     
     836        if where : 
    826837            query += " AND %s" % " AND ".join(where) 
    827838        query += " ORDER BY jobhistory.id DESC" 
    828839        if limit : 
    829840            query += " LIMIT %s" % self.doQuote(int(limit)) 
    830         jobs = []     
    831         result = self.doSearch(query)     
     841        jobs = [] 
     842        result = self.doSearch(query) 
    832843        if result : 
    833844            for fields in result : 
     
    835846                jobs.append(job) 
    836847        return jobs 
    837          
    838     def deleteUser(self, user) :     
     848 
     849    def deleteUser(self, user) : 
    839850        """Completely deletes an user from the database.""" 
    840851        # TODO : What should we do if we delete the last person who used a given printer ? 
    841852        # TODO : we can't reassign the last job to the previous one, because next user would be 
    842853        # TODO : incorrectly charged (overcharged). 
    843         for q in [  
     854        for q in [ 
    844855                    "DELETE FROM payments WHERE userid=%s" % self.doQuote(user.ident), 
    845856                    "DELETE FROM groupsmembers WHERE userid=%s" % self.doQuote(user.ident), 
     
    849860                  ] : 
    850861            self.doModify(q) 
    851              
    852     def multipleQueriesInTransaction(self, queries) :         
     862 
     863    def multipleQueriesInTransaction(self, queries) : 
    853864        """Does many modifications in a single transaction.""" 
    854865        self.beginTransaction() 
     
    856867            for q in queries : 
    857868                self.doModify(q) 
    858         except :     
     869        except : 
    859870            self.rollbackTransaction() 
    860871            raise 
    861         else :     
     872        else : 
    862873            self.commitTransaction() 
    863              
    864     def deleteManyBillingCodes(self, billingcodes) :         
     874 
     875    def deleteManyBillingCodes(self, billingcodes) : 
    865876        """Deletes many billing codes.""" 
    866877        codeids = ", ".join(["%s" % self.doQuote(b.ident) for b in billingcodes]) 
    867878        if codeids : 
    868             self.multipleQueriesInTransaction([  
     879            self.multipleQueriesInTransaction([ 
    869880                    "DELETE FROM billingcodes WHERE id IN (%s)" % codeids,]) 
    870              
    871     def deleteManyUsers(self, users) :         
     881 
     882    def deleteManyUsers(self, users) : 
    872883        """Deletes many users.""" 
    873884        userids = ", ".join(["%s" % self.doQuote(u.ident) for u in users]) 
    874885        if userids : 
    875             self.multipleQueriesInTransaction([  
     886            self.multipleQueriesInTransaction([ 
    876887                    "DELETE FROM payments WHERE userid IN (%s)" % userids, 
    877888                    "DELETE FROM groupsmembers WHERE userid IN (%s)" % userids, 
     
    879890                    "DELETE FROM userpquota WHERE userid IN (%s)" % userids, 
    880891                    "DELETE FROM users WHERE id IN (%s)" % userids,]) 
    881                      
    882     def deleteManyGroups(self, groups) :         
     892 
     893    def deleteManyGroups(self, groups) : 
    883894        """Deletes many groups.""" 
    884895        groupids = ", ".join(["%s" % self.doQuote(g.ident) for g in groups]) 
    885896        if groupids : 
    886             self.multipleQueriesInTransaction([  
     897            self.multipleQueriesInTransaction([ 
    887898                    "DELETE FROM groupsmembers WHERE groupid IN (%s)" % groupids, 
    888899                    "DELETE FROM grouppquota WHERE groupid IN (%s)" % groupids, 
    889900                    "DELETE FROM groups WHERE id IN (%s)" % groupids,]) 
    890          
     901 
    891902    def deleteManyPrinters(self, printers) : 
    892903        """Deletes many printers.""" 
    893904        printerids = ", ".join(["%s" % self.doQuote(p.ident) for p in printers]) 
    894905        if printerids : 
    895             self.multipleQueriesInTransaction([  
     906            self.multipleQueriesInTransaction([ 
    896907                    "DELETE FROM printergroupsmembers WHERE groupid IN (%s) OR printerid IN (%s)" % (printerids, printerids), 
    897908                    "DELETE FROM jobhistory WHERE printerid IN (%s)" % printerids, 
     
    899910                    "DELETE FROM userpquota WHERE printerid IN (%s)" % printerids, 
    900911                    "DELETE FROM printers WHERE id IN (%s)" % printerids,]) 
    901          
    902     def deleteManyUserPQuotas(self, printers, users) :         
     912 
     913    def deleteManyUserPQuotas(self, printers, users) : 
    903914        """Deletes many user print quota entries.""" 
    904915        printerids = ", ".join(["%s" % self.doQuote(p.ident) for p in printers]) 
    905916        userids = ", ".join(["%s" % self.doQuote(u.ident) for u in users]) 
    906917        if userids and printerids : 
    907             self.multipleQueriesInTransaction([  
     918            self.multipleQueriesInTransaction([ 
    908919                    "DELETE FROM jobhistory WHERE userid IN (%s) AND printerid IN (%s)" \ 
    909920                                 % (userids, printerids), 
    910921                    "DELETE FROM userpquota WHERE userid IN (%s) AND printerid IN (%s)" \ 
    911922                                 % (userids, printerids),]) 
    912              
     923 
    913924    def deleteManyGroupPQuotas(self, printers, groups) : 
    914925        """Deletes many group print quota entries.""" 
     
    916927        groupids = ", ".join(["%s" % self.doQuote(g.ident) for g in groups]) 
    917928        if groupids and printerids : 
    918             self.multipleQueriesInTransaction([  
     929            self.multipleQueriesInTransaction([ 
    919930                    "DELETE FROM grouppquota WHERE groupid IN (%s) AND printerid IN (%s)" \ 
    920931                                 % (groupids, printerids),]) 
    921          
    922     def deleteUserPQuota(self, upquota) :     
     932 
     933    def deleteUserPQuota(self, upquota) : 
    923934        """Completely deletes an user print quota entry from the database.""" 
    924         for q in [  
     935        for q in [ 
    925936                    "DELETE FROM jobhistory WHERE userid=%s AND printerid=%s" \ 
    926937                                 % (self.doQuote(upquota.User.ident), self.doQuote(upquota.Printer.ident)), 
     
    928939                  ] : 
    929940            self.doModify(q) 
    930          
    931     def deleteGroupPQuota(self, gpquota) :     
     941 
     942    def deleteGroupPQuota(self, gpquota) : 
    932943        """Completely deletes a group print quota entry from the database.""" 
    933         for q in [  
     944        for q in [ 
    934945                    "DELETE FROM grouppquota WHERE id=%s" % self.doQuote(gpquota.ident), 
    935946                  ] : 
    936947            self.doModify(q) 
    937          
    938     def deleteGroup(self, group) :     
     948 
     949    def deleteGroup(self, group) : 
    939950        """Completely deletes a group from the database.""" 
    940951        for q in [ 
     
    942953                   "DELETE FROM grouppquota WHERE groupid=%s" % self.doQuote(group.ident), 
    943954                   "DELETE FROM groups WHERE id=%s" % self.doQuote(group.ident), 
    944                  ] :   
     955                 ] : 
    945956            self.doModify(q) 
    946              
    947     def deletePrinter(self, printer) :     
     957 
     958    def deletePrinter(self, printer) : 
    948959        """Completely deletes a printer from the database.""" 
    949         for q in [  
     960        for q in [ 
    950961                    "DELETE FROM printergroupsmembers WHERE groupid=%s OR printerid=%s" % (self.doQuote(printer.ident), self.doQuote(printer.ident)), 
    951962                    "DELETE FROM jobhistory WHERE printerid=%s" % self.doQuote(printer.ident), 
     
    955966                  ] : 
    956967            self.doModify(q) 
    957              
    958     def deleteBillingCode(self, code) :     
     968 
     969    def deleteBillingCode(self, code) : 
    959970        """Completely deletes a billing code from the database.""" 
    960971        for q in [ 
    961972                   "DELETE FROM billingcodes WHERE id=%s" % self.doQuote(code.ident), 
    962                  ] :   
     973                 ] : 
    963974            self.doModify(q) 
    964          
     975