Changeset 3522 for pykota/branches
- Timestamp:
- 04/15/10 01:27:45 (15 years ago)
- 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 14 14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 15 # GNU General Public License for more details. 16 # 16 # 17 17 # You should have received a copy of the GNU General Public License 18 18 # along with this program; if not, write to the Free Software … … 25 25 """This module defines a class to access to an LDAP database backend. 26 26 27 My IANA assigned number, for 28 "Conseil Internet & Logiciels Libres, J�me Alet" 27 My IANA assigned number, for 28 "Conseil Internet & Logiciels Libres, J�me Alet" 29 29 is 16868. Use this as a base to extend the LDAP schema. 30 30 """ … … 47 47 import ldap 48 48 import ldap.modlist 49 except ImportError : 49 except ImportError : 50 50 raise PyKotaStorageError, "This python version (%s) doesn't seem to have the python-ldap module installed correctly." % sys.version.split()[0] 51 else : 51 else : 52 52 try : 53 53 from ldap.cidict import cidict 54 except ImportError : 54 except ImportError : 55 55 import UserDict 56 56 sys.stderr.write("ERROR: PyKota requires a newer version of python-ldap. Workaround activated. Please upgrade python-ldap !\n") 57 57 class cidict(UserDict.UserDict) : 58 58 pass # Fake it all, and don't care for case insensitivity : users who need it will have to upgrade. 59 59 60 60 class Storage(BaseStorage) : 61 61 def __init__(self, pykotatool, host, dbname, user, passwd) : … … 67 67 self.savedpasswd = passwd 68 68 self.secondStageInit() 69 70 def secondStageInit(self) : 69 70 def secondStageInit(self) : 71 71 """Second stage initialisation.""" 72 72 BaseStorage.__init__(self, self.savedtool) … … 76 76 try : 77 77 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) 79 79 if self.info["ldaptls"] : 80 80 # we want TLS … … 84 84 self.database.simple_bind_s(self.saveduser, self.savedpasswd) 85 85 self.basedn = self.saveddbname 86 except ldap.SERVER_DOWN : 86 except ldap.SERVER_DOWN : 87 87 message = "LDAP backend for PyKota seems to be down !" 88 88 self.tool.printInfo("%s" % message, "error") 89 89 self.tool.printInfo("Trying again in 2 seconds...", "warn") 90 90 time.sleep(2) 91 except ldap.LDAPError : 91 except ldap.LDAPError : 92 92 message = "Unable to connect to LDAP server %s as %s." % (self.savedhost, self.saveduser) 93 93 self.tool.printInfo("%s" % message, "error") 94 94 self.tool.printInfo("Trying again in 2 seconds...", "warn") 95 95 time.sleep(2) 96 else : 96 else : 97 97 self.useldapcache = self.tool.config.getLDAPCache() 98 98 if self.useldapcache : … … 102 102 self.tool.logdebug("Database opened (host=%s, dbname=%s, user=%s)" % (self.savedhost, self.saveddbname, self.saveduser)) 103 103 return # All is fine here. 104 raise PyKotaStorageError, message 105 106 def close(self) : 104 raise PyKotaStorageError, message 105 106 def close(self) : 107 107 """Closes the database connection.""" 108 108 if not self.closed : … … 110 110 self.closed = 1 111 111 self.tool.logdebug("Database closed.") 112 113 def genUUID(self) : 112 113 def genUUID(self) : 114 114 """Generates an unique identifier. 115 115 116 116 TODO : this one is not unique accross several print servers, but should be sufficient for testing. 117 117 """ 118 118 return md5.md5("%s-%s" % (time.time(), random.random())).hexdigest() 119 120 def normalizeFields(self, fields) : 119 120 def normalizeFields(self, fields) : 121 121 """Ensure all items are lists.""" 122 122 for (k, v) in fields.items() : … … 124 124 if not v : 125 125 del fields[k] 126 else : 126 else : 127 127 fields[k] = [ v ] 128 return fields 129 130 def beginTransaction(self) : 128 return fields 129 130 def beginTransaction(self) : 131 131 """Starts a transaction.""" 132 132 self.tool.logdebug("Transaction begins... WARNING : No transactions in LDAP !") 133 134 def commitTransaction(self) : 133 134 def commitTransaction(self) : 135 135 """Commits a transaction.""" 136 136 self.tool.logdebug("Transaction committed. WARNING : No transactions in LDAP !") 137 138 def rollbackTransaction(self) : 137 138 def rollbackTransaction(self) : 139 139 """Rollbacks a transaction.""" 140 140 self.tool.logdebug("Transaction aborted. WARNING : No transaction in LDAP !") 141 141 142 142 def doSearch(self, key, fields=None, base="", scope=ldap.SCOPE_SUBTREE, flushcache=0) : 143 143 """Does an LDAP search query.""" … … 150 150 # retrieve ALL user defined attributes ("*") 151 151 # + the createTimestamp attribute, needed by job history 152 # 152 # 153 153 # 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) 155 155 # and iPlanet Directory Server (5.1 SP3) 156 fields = ["*", "createTimestamp"] 157 156 fields = ["*", "createTimestamp"] 157 158 158 if self.useldapcache and (not flushcache) and (scope == ldap.SCOPE_BASE) and self.ldapcache.has_key(base) : 159 159 entry = self.ldapcache[base] … … 163 163 self.tool.logdebug("QUERY : Filter : %s, BaseDN : %s, Scope : %s, Attributes : %s" % (key, base, scope, fields)) 164 164 result = self.database.search_s(base, scope, key, fields) 165 except ldap.NO_SUCH_OBJECT, msg : 165 except ldap.NO_SUCH_OBJECT, msg : 166 166 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 : 168 168 message = (_("Search for %s(%s) from %s(scope=%s) returned no answer.") % (key, fields, base, scope)) + " : %s" % str(msg) 169 169 self.tool.printInfo("LDAP error : %s" % message, "error") … … 171 171 self.close() 172 172 self.secondStageInit() 173 else : 173 else : 174 174 self.tool.logdebug("QUERY : Result : %s" % result) 175 175 result = [ (dn, cidict(attrs)) for (dn, attrs) in result ] … … 180 180 return result 181 181 raise PyKotaStorageError, message 182 182 183 183 def doAdd(self, dn, fields) : 184 184 """Adds an entry in the LDAP directory.""" … … 191 191 self.tool.logdebug("%s" % entry) 192 192 self.database.add_s(dn, entry) 193 except ldap.ALREADY_EXISTS, msg : 193 except ldap.ALREADY_EXISTS, msg : 194 194 raise PyKotaStorageError, "Entry %s already exists : %s" % (dn, str(msg)) 195 195 except ldap.LDAPError, msg : … … 205 205 return dn 206 206 raise PyKotaStorageError, message 207 207 208 208 def doDelete(self, dn) : 209 209 """Deletes an entry from the LDAP directory.""" … … 213 213 self.tool.logdebug("QUERY : Delete(%s)" % dn) 214 214 self.database.delete_s(dn) 215 except ldap.NO_SUCH_OBJECT : 215 except ldap.NO_SUCH_OBJECT : 216 216 self.tool.printInfo("Entry %s was already missing before we deleted it. This **MAY** be normal." % dn, "info") 217 217 except ldap.LDAPError, msg : … … 221 221 self.close() 222 222 self.secondStageInit() 223 else : 223 else : 224 224 if self.useldapcache : 225 225 try : 226 226 self.tool.logdebug("LDAP cache del %s" % dn) 227 227 del self.ldapcache[dn] 228 except KeyError : 228 except KeyError : 229 229 pass 230 return 230 return 231 231 raise PyKotaStorageError, message 232 232 233 233 def doModify(self, dn, fields, ignoreold=1, flushcache=0) : 234 234 """Modifies an entry in the LDAP directory.""" … … 245 245 if k != "createTimestamp" : 246 246 oldentry[k] = v 247 else : 247 else : 248 248 self.tool.logdebug("LDAP cache miss %s" % dn) 249 249 oldentry = self.doSearch("objectClass=*", base=dn, scope=ldap.SCOPE_BASE)[0][1] 250 else : 250 else : 251 251 oldentry = self.doSearch("objectClass=*", base=dn, scope=ldap.SCOPE_BASE, flushcache=flushcache)[0][1] 252 252 for (k, v) in fields.items() : … … 254 254 try : 255 255 oldvalue = v["convert"](oldentry.get(k, [0])[0]) 256 except ValueError : 256 except ValueError : 257 257 self.tool.logdebug("Error converting %s with %s(%s)" % (oldentry.get(k), k, v)) 258 258 oldvalue = 0 259 259 if v["operator"] == '+' : 260 260 newvalue = oldvalue + v["value"] 261 else : 261 else : 262 262 newvalue = oldvalue - v["value"] 263 263 fields[k] = str(newvalue) … … 287 287 try : 288 288 del cachedentry[mtyp] 289 except KeyError : 289 except KeyError : 290 290 pass 291 291 self.tool.logdebug("LDAP cache update %s => %s" % (dn, cachedentry)) 292 292 return dn 293 293 raise PyKotaStorageError, message 294 294 295 295 def filterNames(self, records, attribute, patterns=None) : 296 296 """Returns a list of 'attribute' from a list of records. 297 297 298 298 Logs any missing attribute. 299 """ 299 """ 300 300 result = [] 301 301 for (dn, record) in records : … … 308 308 if (not isinstance(patterns, type([]))) and (not isinstance(patterns, type(()))) : 309 309 patterns = [ patterns ] 310 if self.tool.matchString(attrval, patterns) : 310 if self.tool.matchString(attrval, patterns) : 311 311 result.append(attrval) 312 else : 312 else : 313 313 result.append(attrval) 314 return result 315 316 def getAllBillingCodes(self, billingcode=None) : 314 return result 315 316 def getAllBillingCodes(self, billingcode=None) : 317 317 """Extracts all billing codes or only the billing codes matching the optional parameter.""" 318 318 ldapfilter = "objectClass=pykotaBilling" … … 320 320 if result : 321 321 return [self.databaseToUserCharset(bc) for bc in self.filterNames(result, "pykotaBillingCode", billingcode)] 322 else : 322 else : 323 323 return [] 324 325 def getAllPrintersNames(self, printername=None) : 324 325 def getAllPrintersNames(self, printername=None) : 326 326 """Extracts all printer names or only the printers' names matching the optional parameter.""" 327 327 ldapfilter = "objectClass=pykotaPrinter" … … 329 329 if result : 330 330 return self.filterNames(result, "pykotaPrinterName", printername) 331 else : 331 else : 332 332 return [] 333 334 def getAllUsersNames(self, username=None) : 333 334 def getAllUsersNames(self, username=None) : 335 335 """Extracts all user names or only the users' names matching the optional parameter.""" 336 336 ldapfilter = "objectClass=pykotaAccount" … … 338 338 if result : 339 339 return self.filterNames(result, "pykotaUserName", username) 340 else : 340 else : 341 341 return [] 342 343 def getAllGroupsNames(self, groupname=None) : 342 343 def getAllGroupsNames(self, groupname=None) : 344 344 """Extracts all group names or only the groups' names matching the optional parameter.""" 345 345 ldapfilter = "objectClass=pykotaGroup" … … 347 347 if result : 348 348 return self.filterNames(result, "pykotaGroupName", groupname) 349 else : 349 else : 350 350 return [] 351 351 352 352 def getUserNbJobsFromHistory(self, user) : 353 353 """Returns the number of jobs the user has in history.""" 354 354 result = self.doSearch("(&(pykotaUserName=%s)(objectClass=pykotaJob))" % self.userCharsetToDatabase(user.Name), None, base=self.info["jobbase"]) 355 355 return len(result) 356 357 def getUserFromBackend(self, username) : 356 357 def getUserFromBackend(self, username) : 358 358 """Extracts user information given its name.""" 359 359 user = StorageUser(self, username) … … 376 376 if user.AccountBalance[0].upper() == "NONE" : 377 377 user.AccountBalance = None 378 else : 378 else : 379 379 user.AccountBalance = float(user.AccountBalance[0]) 380 user.AccountBalance = user.AccountBalance or 0.0 380 user.AccountBalance = user.AccountBalance or 0.0 381 381 user.LifeTimePaid = fields.get("pykotaLifeTimePaid") 382 382 user.OverCharge = float(fields.get("pykotaOverCharge", [1.0])[0]) … … 384 384 if user.LifeTimePaid[0].upper() == "NONE" : 385 385 user.LifeTimePaid = None 386 else : 386 else : 387 387 user.LifeTimePaid = float(user.LifeTimePaid[0]) 388 user.LifeTimePaid = user.LifeTimePaid or 0.0 388 user.LifeTimePaid = user.LifeTimePaid or 0.0 389 389 user.Payments = [] 390 390 for payment in fields.get("pykotaPayments", []) : … … 395 395 (date, amount) = payment.split(" # ") 396 396 description = "" 397 else : 397 else : 398 398 description = self.databaseToUserCharset(base64.decodestring(description)) 399 399 user.Payments.append((date, float(amount), description)) 400 400 user.Exists = True 401 401 return user 402 403 def getGroupFromBackend(self, groupname) : 402 403 def getGroupFromBackend(self, groupname) : 404 404 """Extracts group information given its name.""" 405 405 group = StorageGroup(self, groupname) … … 409 409 fields = result[0][1] 410 410 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] 412 412 group.Description = self.databaseToUserCharset(fields.get("description", [None])[0]) 413 413 group.LimitBy = fields.get("pykotaLimitBy", ["quota"])[0] … … 420 420 group.Exists = True 421 421 return group 422 423 def getPrinterFromBackend(self, printername) : 422 423 def getPrinterFromBackend(self, printername) : 424 424 """Extracts printer information given its name : returns first matching printer.""" 425 425 printer = StoragePrinter(self, printername) … … 434 434 fields = result[0][1] # take only first matching printer, ignore the rest 435 435 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] 437 437 printer.PricePerJob = float(fields.get("pykotaPricePerJob", [0.0])[0]) 438 438 printer.PricePerPage = float(fields.get("pykotaPricePerPage", [0.0])[0]) … … 444 444 printer.PassThrough = 0 445 445 printer.uniqueMember = fields.get("uniqueMember", []) 446 printer.Description = self.databaseToUserCharset(fields.get("description", [""])[0]) 446 printer.Description = self.databaseToUserCharset(fields.get("description", [""])[0]) 447 447 printer.Exists = True 448 return printer 449 450 def getUserPQuotaFromBackend(self, user, printer) : 448 return printer 449 450 def getUserPQuotaFromBackend(self, user, printer) : 451 451 """Extracts a user print quota.""" 452 452 userpquota = StorageUserPQuota(self, user, printer) … … 454 454 if self.info["userquotabase"].lower() == "user" : 455 455 base = user.ident 456 else : 456 else : 457 457 base = self.info["userquotabase"] 458 458 result = self.doSearch("(&(objectClass=pykotaUserPQuota)(pykotaUserName=%s)(pykotaPrinterName=%s))" % \ … … 470 470 if userpquota.SoftLimit[0].upper() == "NONE" : 471 471 userpquota.SoftLimit = None 472 else : 472 else : 473 473 userpquota.SoftLimit = int(userpquota.SoftLimit[0]) 474 474 userpquota.HardLimit = fields.get("pykotaHardLimit") … … 476 476 if userpquota.HardLimit[0].upper() == "NONE" : 477 477 userpquota.HardLimit = None 478 elif userpquota.HardLimit is not None : 478 elif userpquota.HardLimit is not None : 479 479 userpquota.HardLimit = int(userpquota.HardLimit[0]) 480 480 userpquota.DateLimit = fields.get("pykotaDateLimit") 481 481 if userpquota.DateLimit is not None : 482 if userpquota.DateLimit[0].upper() == "NONE" : 482 if userpquota.DateLimit[0].upper() == "NONE" : 483 483 userpquota.DateLimit = None 484 else : 484 else : 485 485 userpquota.DateLimit = userpquota.DateLimit[0] 486 486 userpquota.MaxJobSize = fields.get("pykotaMaxJobSize") … … 488 488 if userpquota.MaxJobSize[0].upper() == "NONE" : 489 489 userpquota.MaxJobSize = None 490 else : 490 else : 491 491 userpquota.MaxJobSize = int(userpquota.MaxJobSize[0]) 492 492 userpquota.Exists = True 493 493 return userpquota 494 495 def getGroupPQuotaFromBackend(self, group, printer) : 494 495 def getGroupPQuotaFromBackend(self, group, printer) : 496 496 """Extracts a group print quota.""" 497 497 grouppquota = StorageGroupPQuota(self, group, printer) … … 499 499 if self.info["groupquotabase"].lower() == "group" : 500 500 base = group.ident 501 else : 501 else : 502 502 base = self.info["groupquotabase"] 503 503 result = self.doSearch("(&(objectClass=pykotaGroupPQuota)(pykotaGroupName=%s)(pykotaPrinterName=%s))" % \ … … 512 512 if grouppquota.SoftLimit[0].upper() == "NONE" : 513 513 grouppquota.SoftLimit = None 514 else : 514 else : 515 515 grouppquota.SoftLimit = int(grouppquota.SoftLimit[0]) 516 516 grouppquota.HardLimit = fields.get("pykotaHardLimit") … … 518 518 if grouppquota.HardLimit[0].upper() == "NONE" : 519 519 grouppquota.HardLimit = None 520 else : 520 else : 521 521 grouppquota.HardLimit = int(grouppquota.HardLimit[0]) 522 522 grouppquota.DateLimit = fields.get("pykotaDateLimit") 523 523 if grouppquota.DateLimit is not None : 524 if grouppquota.DateLimit[0].upper() == "NONE" : 524 if grouppquota.DateLimit[0].upper() == "NONE" : 525 525 grouppquota.DateLimit = None 526 else : 526 else : 527 527 grouppquota.DateLimit = grouppquota.DateLimit[0] 528 528 grouppquota.MaxJobSize = fields.get("pykotaMaxJobSize") … … 530 530 if grouppquota.MaxJobSize[0].upper() == "NONE" : 531 531 grouppquota.MaxJobSize = None 532 else : 532 else : 533 533 grouppquota.MaxJobSize = int(grouppquota.MaxJobSize[0]) 534 534 grouppquota.PageCounter = 0 … … 545 545 ["pykotaPageCounter", "pykotaLifePageCounter"], base=base) 546 546 if result : 547 for userpquota in result : 547 for userpquota in result : 548 548 grouppquota.PageCounter += int(userpquota[1].get("pykotaPageCounter", [0])[0] or 0) 549 549 grouppquota.LifePageCounter += int(userpquota[1].get("pykotaLifePageCounter", [0])[0] or 0) 550 550 grouppquota.Exists = True 551 551 return grouppquota 552 553 def getPrinterLastJobFromBackend(self, printer) : 552 553 def getPrinterLastJobFromBackend(self, printer) : 554 554 """Extracts a printer's last job information.""" 555 555 lastjob = StorageLastJob(self, printer) … … 564 564 result = None 565 565 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", 582 582 "pykotaPrecomputedJobSize", 583 583 "pykotaPrecomputedJobPrice", 584 "createTimestamp" ], 584 "createTimestamp" ], 585 585 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. 588 588 if result : 589 589 fields = result[0][1] … … 594 594 try : 595 595 lastjob.JobSize = int(fields.get("pykotaJobSize", [0])[0]) 596 except ValueError : 596 except ValueError : 597 597 lastjob.JobSize = None 598 try : 598 try : 599 599 lastjob.JobPrice = float(fields.get("pykotaJobPrice", [0.0])[0]) 600 except ValueError : 600 except ValueError : 601 601 lastjob.JobPrice = None 602 602 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]) 605 605 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]) 607 607 lastjob.JobHostName = fields.get("pykotaHostName", [""])[0] 608 608 lastjob.JobSizeBytes = fields.get("pykotaJobSizeBytes", [0L])[0] … … 612 612 try : 613 613 lastjob.PrecomputedJobSize = int(fields.get("pykotaPrecomputedJobSize", [0])[0]) 614 except ValueError : 614 except ValueError : 615 615 lastjob.PrecomputedJobSize = None 616 try : 616 try : 617 617 lastjob.PrecomputedJobPrice = float(fields.get("pykotaPrecomputedJobPrice", [0.0])[0]) 618 except ValueError : 618 except ValueError : 619 619 lastjob.PrecomputedJobPrice = None 620 620 if lastjob.JobTitle == lastjob.JobFileName == lastjob.JobOptions == "hidden" : … … 625 625 lastjob.Exists = True 626 626 return lastjob 627 628 def getGroupMembersFromBackend(self, group) : 627 628 def getGroupMembersFromBackend(self, group) : 629 629 """Returns the group's members list.""" 630 630 groupmembers = [] … … 637 637 for username in result[0][1].get(self.info["groupmembers"], []) : 638 638 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) : 642 642 """Returns the user's groups list.""" 643 643 groups = [] … … 657 657 if group.LimitBy is not None : 658 658 group.LimitBy = group.LimitBy[0] 659 else : 659 else : 660 660 group.LimitBy = "quota" 661 661 group.AccountBalance = 0.0 … … 668 668 self.cacheEntry("GROUPS", group.Name, group) 669 669 groups.append(group) 670 return groups 671 672 def getParentPrintersFromBackend(self, printer) : 670 return groups 671 672 def getParentPrintersFromBackend(self, printer) : 673 673 """Get all the printer groups this printer is a member of.""" 674 674 pgroups = [] … … 684 684 pgroups.append(parentprinter) 685 685 return pgroups 686 686 687 687 def getMatchingPrinters(self, printerpattern) : 688 688 """Returns the list of all printers for which name matches a certain pattern.""" … … 694 694 if result : 695 695 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) 703 697 for (printerid, fields) in result : 704 698 printername = self.databaseToUserCharset(fields.get("pykotaPrinterName", [""])[0] or fields.get(self.info["printerrdn"], [""])[0]) … … 715 709 printer.PassThrough = 0 716 710 printer.uniqueMember = fields.get("uniqueMember", []) 717 printer.Description = self.databaseToUserCharset(fields.get("description", [""])[0]) 711 printer.Description = self.databaseToUserCharset(fields.get("description", [""])[0]) 718 712 printer.Exists = True 719 713 printers.append(printer) 720 714 self.cacheEntry("PRINTERS", printer.Name, printer) 721 return printers 722 715 return printers 716 723 717 def getMatchingUsers(self, userpattern) : 724 718 """Returns the list of all users for which name matches a certain pattern.""" … … 730 724 if result : 731 725 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) 739 727 for (userid, fields) in result : 740 728 username = self.databaseToUserCharset(fields.get("pykotaUserName", [""])[0] or fields.get(self.info["userrdn"], [""])[0]) … … 744 732 user.Email = fields.get(self.info["usermail"], [None])[0] 745 733 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]) 747 735 uname = self.userCharsetToDatabase(username) 748 736 result = self.doSearch("(&(objectClass=pykotaAccountBalance)(|(pykotaUserName=%s)(%s=%s)))" % \ … … 760 748 if user.AccountBalance[0].upper() == "NONE" : 761 749 user.AccountBalance = None 762 else : 750 else : 763 751 user.AccountBalance = float(user.AccountBalance[0]) 764 user.AccountBalance = user.AccountBalance or 0.0 752 user.AccountBalance = user.AccountBalance or 0.0 765 753 user.LifeTimePaid = fields.get("pykotaLifeTimePaid") 766 754 if user.LifeTimePaid is not None : 767 755 if user.LifeTimePaid[0].upper() == "NONE" : 768 756 user.LifeTimePaid = None 769 else : 757 else : 770 758 user.LifeTimePaid = float(user.LifeTimePaid[0]) 771 user.LifeTimePaid = user.LifeTimePaid or 0.0 759 user.LifeTimePaid = user.LifeTimePaid or 0.0 772 760 user.Payments = [] 773 761 for payment in fields.get("pykotaPayments", []) : … … 778 766 (date, amount) = payment.split(" # ") 779 767 description = "" 780 else : 768 else : 781 769 description = self.databaseToUserCharset(base64.decodestring(description)) 782 770 user.Payments.append((date, float(amount), description)) … … 784 772 users.append(user) 785 773 self.cacheEntry("USERS", user.Name, user) 786 return users 787 774 return users 775 788 776 def getMatchingGroups(self, grouppattern) : 789 777 """Returns the list of all groups for which name matches a certain pattern.""" … … 795 783 if result : 796 784 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) 804 786 for (groupid, fields) in result : 805 787 groupname = self.databaseToUserCharset(fields.get("pykotaGroupName", [""])[0] or fields.get(self.info["grouprdn"], [""])[0]) … … 807 789 group = StorageGroup(self, groupname) 808 790 group.ident = groupid 809 group.Name = fields.get("pykotaGroupName", [self.databaseToUserCharset(groupname)])[0] 791 group.Name = fields.get("pykotaGroupName", [self.databaseToUserCharset(groupname)])[0] 810 792 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]) 812 794 group.AccountBalance = 0.0 813 795 group.LifeTimePaid = 0.0 … … 820 802 self.cacheEntry("GROUPS", group.Name, group) 821 803 return groups 822 823 def getPrinterUsersAndQuotas(self, printer, names=["*"]) : 804 805 def getPrinterUsersAndQuotas(self, printer, names=["*"]) : 824 806 """Returns the list of users who uses a given printer, along with their quotas.""" 825 807 usersandquotas = [] … … 846 828 if userpquota.SoftLimit[0].upper() == "NONE" : 847 829 userpquota.SoftLimit = None 848 else : 830 else : 849 831 userpquota.SoftLimit = int(userpquota.SoftLimit[0]) 850 832 userpquota.HardLimit = fields.get("pykotaHardLimit") … … 852 834 if userpquota.HardLimit[0].upper() == "NONE" : 853 835 userpquota.HardLimit = None 854 elif userpquota.HardLimit is not None : 836 elif userpquota.HardLimit is not None : 855 837 userpquota.HardLimit = int(userpquota.HardLimit[0]) 856 838 userpquota.DateLimit = fields.get("pykotaDateLimit") 857 839 if userpquota.DateLimit is not None : 858 if userpquota.DateLimit[0].upper() == "NONE" : 840 if userpquota.DateLimit[0].upper() == "NONE" : 859 841 userpquota.DateLimit = None 860 else : 842 else : 861 843 userpquota.DateLimit = userpquota.DateLimit[0] 862 844 userpquota.Exists = True 863 845 usersandquotas.append((user, userpquota)) 864 846 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)) 866 848 return usersandquotas 867 868 def getPrinterGroupsAndQuotas(self, printer, names=["*"]) : 849 850 def getPrinterGroupsAndQuotas(self, printer, names=["*"]) : 869 851 """Returns the list of groups which uses a given printer, along with their quotas.""" 870 852 groupsandquotas = [] … … 884 866 grouppquota = self.getGroupPQuota(group, printer) 885 867 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)) 887 869 return groupsandquotas 888 870 889 871 def addPrinter(self, printer) : 890 872 """Adds a printer to the quota storage, returns the old value if it already exists.""" … … 902 884 "pykotaPricePerPage" : str(printer.PricePerPage or 0.0), 903 885 "pykotaPricePerJob" : str(printer.PricePerJob or 0.0), 904 } 886 } 905 887 dn = "%s=%s,%s" % (self.info["printerrdn"], printername, self.info["printerbase"]) 906 888 self.doAdd(dn, fields) 907 889 printer.isDirty = False 908 890 return None # the entry created doesn't need further modification 909 910 def addUser(self, user) : 891 892 def addUser(self, user) : 911 893 """Adds a user to the quota storage, returns the old value if it already exists.""" 912 894 oldentry = self.getUser(user.Name) … … 919 901 "description" : self.userCharsetToDatabase(user.Description or ""), 920 902 self.info["usermail"] : user.Email or "", 921 } 922 903 } 904 923 905 mustadd = 1 924 906 if self.info["newuser"].lower() != 'below' : … … 938 920 fields.update({ "pykotaBalance" : str(user.AccountBalance or 0.0), 939 921 "pykotaOverCharge" : str(user.OverCharge), 940 "pykotaLifeTimePaid" : str(user.LifeTimePaid or 0.0), }) 922 "pykotaLifeTimePaid" : str(user.LifeTimePaid or 0.0), }) 941 923 self.doModify(dn, fields) 942 924 mustadd = 0 943 925 else : 944 926 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" : 946 928 self.tool.printInfo(_("%s. A new entry will be created instead.") % message, "warn") 947 929 else : # 'fail' or incorrect setting 948 930 raise PyKotaStorageError, "%s. Action aborted. Please check your configuration." % message 949 931 950 932 if mustadd : 951 if self.info["userbase"] == self.info["balancebase"] : 933 if self.info["userbase"] == self.info["balancebase"] : 952 934 fields = { self.info["userrdn"] : uname, 953 935 "objectClass" : ["pykotaObject", "pykotaAccount", "pykotaAccountBalance"], … … 955 937 "pykotaBalance" : str(user.AccountBalance or 0.0), 956 938 "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 : 960 942 fields = { self.info["userrdn"] : uname, 961 943 "objectClass" : ["pykotaObject", "pykotaAccount"], 962 944 "cn" : uname, 963 } 964 fields.update(newfields) 945 } 946 fields.update(newfields) 965 947 dn = "%s=%s,%s" % (self.info["userrdn"], uname, self.info["userbase"]) 966 948 self.doAdd(dn, fields) 967 if self.info["userbase"] != self.info["balancebase"] : 949 if self.info["userbase"] != self.info["balancebase"] : 968 950 fields = { self.info["balancerdn"] : uname, 969 951 "objectClass" : ["pykotaObject", "pykotaAccountBalance"], … … 971 953 "pykotaBalance" : str(user.AccountBalance or 0.0), 972 954 "pykotaOverCharge" : str(user.OverCharge), 973 "pykotaLifeTimePaid" : str(user.LifeTimePaid or 0.0), 974 } 955 "pykotaLifeTimePaid" : str(user.LifeTimePaid or 0.0), 956 } 975 957 dn = "%s=%s,%s" % (self.info["balancerdn"], uname, self.info["balancebase"]) 976 958 self.doAdd(dn, fields) … … 982 964 user.isDirty = False 983 965 return None # the entry created doesn't need further modification 984 985 def addGroup(self, group) : 966 967 def addGroup(self, group) : 986 968 """Adds a group to the quota storage, returns the old value if it already exists.""" 987 969 oldentry = self.getGroup(group.Name) … … 989 971 return oldentry # we return the existing entry 990 972 gname = self.userCharsetToDatabase(group.Name) 991 newfields = { 973 newfields = { 992 974 "pykotaGroupName" : gname, 993 975 "pykotaLimitBy" : (group.LimitBy or "quota"), 994 976 "description" : self.userCharsetToDatabase(group.Description or "") 995 } 977 } 996 978 mustadd = 1 997 979 if self.info["newgroup"].lower() != 'below' : … … 1013 995 else : 1014 996 message = _("Unable to find an existing entry to attach pykotaGroup objectclass %s") % group.Name 1015 if action.lower() == "warn" : 997 if action.lower() == "warn" : 1016 998 self.tool.printInfo("%s. A new entry will be created instead." % message, "warn") 1017 999 else : # 'fail' or incorrect setting 1018 1000 raise PyKotaStorageError, "%s. Action aborted. Please check your configuration." % message 1019 1001 1020 1002 if mustadd : 1021 1003 fields = { self.info["grouprdn"] : gname, 1022 1004 "objectClass" : ["pykotaObject", "pykotaGroup"], 1023 1005 "cn" : gname, 1024 } 1025 fields.update(newfields) 1006 } 1007 fields.update(newfields) 1026 1008 dn = "%s=%s,%s" % (self.info["grouprdn"], gname, self.info["groupbase"]) 1027 1009 self.doAdd(dn, fields) 1028 1010 group.isDirty = False 1029 1011 return None # the entry created doesn't need further modification 1030 1031 def addUserToGroup(self, user, group) : 1012 1013 def addUserToGroup(self, user, group) : 1032 1014 """Adds an user to a group.""" 1033 1015 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) 1035 1017 if result : 1036 1018 fields = result[0][1] … … 1040 1022 self.doModify(group.ident, fields) 1041 1023 group.Members.append(user) 1042 1043 def delUserFromGroup(self, user, group) : 1024 1025 def delUserFromGroup(self, user, group) : 1044 1026 """Removes an user from a group.""" 1045 1027 if user.Name in [u.Name for u in self.getGroupMembers(group)] : … … 1049 1031 if not fields.has_key(self.info["groupmembers"]) : 1050 1032 fields[self.info["groupmembers"]] = [] 1051 try : 1033 try : 1052 1034 fields[self.info["groupmembers"]].remove(self.userCharsetToDatabase(user.Name)) 1053 1035 except ValueError : … … 1056 1038 self.doModify(group.ident, fields) 1057 1039 group.Members.remove(user) 1058 1040 1059 1041 def addUserPQuota(self, upq) : 1060 1042 """Initializes a user print quota on a printer.""" … … 1077 1059 "pykotaWarnCount" : str(upq.WarnCount or 0), 1078 1060 "pykotaMaxJobSize" : str(upq.MaxJobSize or 0), 1079 } 1061 } 1080 1062 if self.info["userquotabase"].lower() == "user" : 1081 1063 dn = "cn=%s,%s" % (uuid, upq.User.ident) 1082 else : 1064 else : 1083 1065 dn = "cn=%s,%s" % (uuid, self.info["userquotabase"]) 1084 1066 self.doAdd(dn, fields) 1085 1067 upq.isDirty = False 1086 1068 return None # the entry created doesn't need further modification 1087 1069 1088 1070 def addGroupPQuota(self, gpq) : 1089 1071 """Initializes a group print quota on a printer.""" … … 1099 1081 "pykotaPrinterName" : pname, 1100 1082 "pykotaDateLimit" : "None", 1101 } 1083 } 1102 1084 if self.info["groupquotabase"].lower() == "group" : 1103 1085 dn = "cn=%s,%s" % (uuid, gpq.Group.ident) 1104 else : 1086 else : 1105 1087 dn = "cn=%s,%s" % (uuid, self.info["groupquotabase"]) 1106 1088 self.doAdd(dn, fields) 1107 1089 gpq.isDirty = False 1108 1090 return None # the entry created doesn't need further modification 1109 1110 def savePrinter(self, printer) : 1091 1092 def savePrinter(self, printer) : 1111 1093 """Saves the printer to the database in a single operation.""" 1112 1094 fields = { … … 1118 1100 } 1119 1101 self.doModify(printer.ident, fields) 1120 1102 1121 1103 def saveUser(self, user) : 1122 1104 """Saves the user to the database in a single operation.""" 1123 1105 newfields = { 1124 1106 "pykotaLimitBy" : (user.LimitBy or "quota"), 1125 "description" : self.userCharsetToDatabase(user.Description or ""), 1107 "description" : self.userCharsetToDatabase(user.Description or ""), 1126 1108 self.info["usermail"] : user.Email or "", 1127 } 1109 } 1128 1110 self.doModify(user.ident, newfields) 1129 1111 1130 1112 newfields = { "pykotaBalance" : str(user.AccountBalance or 0.0), 1131 "pykotaLifeTimePaid" : str(user.LifeTimePaid or 0.0), 1113 "pykotaLifeTimePaid" : str(user.LifeTimePaid or 0.0), 1132 1114 "pykotaOverCharge" : str(user.OverCharge), 1133 1115 } 1134 1116 self.doModify(user.idbalance, newfields) 1135 1117 1136 1118 def saveGroup(self, group) : 1137 1119 """Saves the group to the database in a single operation.""" 1138 1120 newfields = { 1139 1121 "pykotaLimitBy" : (group.LimitBy or "quota"), 1140 "description" : self.userCharsetToDatabase(group.Description or ""), 1141 } 1122 "description" : self.userCharsetToDatabase(group.Description or ""), 1123 } 1142 1124 self.doModify(group.ident, newfields) 1143 1144 def writeUserPQuotaDateLimit(self, userpquota, datelimit) : 1125 1126 def writeUserPQuotaDateLimit(self, userpquota, datelimit) : 1145 1127 """Sets the date limit permanently for a user print quota.""" 1146 1128 fields = { … … 1148 1130 } 1149 1131 return self.doModify(userpquota.ident, fields) 1150 1151 def writeGroupPQuotaDateLimit(self, grouppquota, datelimit) : 1132 1133 def writeGroupPQuotaDateLimit(self, grouppquota, datelimit) : 1152 1134 """Sets the date limit permanently for a group print quota.""" 1153 1135 fields = { … … 1155 1137 } 1156 1138 return self.doModify(grouppquota.ident, fields) 1157 1158 def increaseUserPQuotaPagesCounters(self, userpquota, nbpages) : 1139 1140 def increaseUserPQuotaPagesCounters(self, userpquota, nbpages) : 1159 1141 """Increase page counters for a user print quota.""" 1160 1142 fields = { … … 1162 1144 "pykotaLifePageCounter" : { "operator" : "+", "value" : nbpages, "convert" : int }, 1163 1145 } 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) : 1167 1149 """Decreases user's account balance from an amount.""" 1168 1150 fields = { 1169 1151 "pykotaBalance" : { "operator" : "-", "value" : amount, "convert" : float }, 1170 1152 } 1171 return self.doModify(user.idbalance, fields, flushcache=1) 1172 1153 return self.doModify(user.idbalance, fields, flushcache=1) 1154 1173 1155 def writeNewPayment(self, user, amount, comment="") : 1174 1156 """Adds a new payment to the payments history.""" … … 1180 1162 "pykotaPayments" : payments, 1181 1163 } 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) : 1185 1167 """Sets the last job's size permanently.""" 1186 1168 fields = { … … 1188 1170 "pykotaJobPrice" : str(jobprice), 1189 1171 } 1190 self.doModify(lastjob.ident, fields) 1191 1172 self.doModify(lastjob.ident, fields) 1173 1192 1174 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) : 1193 1175 """Adds a job in a printer's history.""" … … 1197 1179 uuid = self.genUUID() 1198 1180 dn = "cn=%s,%s" % (uuid, self.info["jobbase"]) 1199 else : 1181 else : 1200 1182 uuid = printer.LastJob.ident[3:].split(",")[0] 1201 1183 dn = printer.LastJob.ident 1202 if self.privacy : 1184 if self.privacy : 1203 1185 # For legal reasons, we want to hide the title, filename and options 1204 1186 title = filename = options = "hidden" … … 1211 1193 "pykotaPrinterPageCounter" : str(pagecounter), 1212 1194 "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), 1218 1200 "pykotaJobSizeBytes" : str(jobsizebytes), 1219 1201 "pykotaMD5Sum" : str(jobmd5sum), … … 1224 1206 } 1225 1207 if (not self.disablehistory) or (not printer.LastJob.Exists) : 1226 if jobsize is not None : 1208 if jobsize is not None : 1227 1209 fields.update({ "pykotaJobSize" : str(jobsize), "pykotaJobPrice" : str(jobprice) }) 1228 1210 self.doAdd(dn, fields) 1229 else : 1211 else : 1230 1212 # here we explicitly want to reset jobsize to 'None' if needed 1231 1213 fields.update({ "pykotaJobSize" : str(jobsize), "pykotaJobPrice" : str(jobprice) }) 1232 1214 self.doModify(dn, fields) 1233 1215 1234 1216 if printer.LastJob.Exists : 1235 1217 fields = { 1236 1218 "pykotaLastJobIdent" : uuid, 1237 1219 } 1238 self.doModify(printer.LastJob.lastjobident, fields) 1239 else : 1220 self.doModify(printer.LastJob.lastjobident, fields) 1221 else : 1240 1222 lastjuuid = self.genUUID() 1241 1223 lastjdn = "cn=%s,%s" % (lastjuuid, self.info["lastjobbase"]) … … 1245 1227 "pykotaPrinterName" : pname, 1246 1228 "pykotaLastJobIdent" : uuid, 1247 } 1248 self.doAdd(lastjdn, fields) 1249 1229 } 1230 self.doAdd(lastjdn, fields) 1231 1250 1232 def saveUserPQuota(self, userpquota) : 1251 1233 """Saves an user print quota entry.""" 1252 fields = { 1234 fields = { 1253 1235 "pykotaSoftLimit" : str(userpquota.SoftLimit), 1254 1236 "pykotaHardLimit" : str(userpquota.HardLimit), … … 1260 1242 } 1261 1243 self.doModify(userpquota.ident, fields) 1262 1244 1263 1245 def writeUserPQuotaWarnCount(self, userpquota, warncount) : 1264 1246 """Sets the warn counter value for a user quota.""" 1265 fields = { 1247 fields = { 1266 1248 "pykotaWarnCount" : str(warncount or 0), 1267 1249 } 1268 1250 self.doModify(userpquota.ident, fields) 1269 1251 1270 1252 def increaseUserPQuotaWarnCount(self, userpquota) : 1271 1253 """Increases the warn counter value for a user quota.""" … … 1273 1255 "pykotaWarnCount" : { "operator" : "+", "value" : 1, "convert" : int }, 1274 1256 } 1275 return self.doModify(userpquota.ident, fields) 1276 1257 return self.doModify(userpquota.ident, fields) 1258 1277 1259 def saveGroupPQuota(self, grouppquota) : 1278 1260 """Saves a group print quota entry.""" 1279 fields = { 1261 fields = { 1280 1262 "pykotaSoftLimit" : str(grouppquota.SoftLimit), 1281 1263 "pykotaHardLimit" : str(grouppquota.HardLimit), … … 1284 1266 } 1285 1267 self.doModify(grouppquota.ident, fields) 1286 1268 1287 1269 def writePrinterToGroup(self, pgroup, printer) : 1288 1270 """Puts a printer into a printer group.""" … … 1291 1273 fields = { 1292 1274 "uniqueMember" : pgroup.uniqueMember 1293 } 1294 self.doModify(pgroup.ident, fields) 1295 1275 } 1276 self.doModify(pgroup.ident, fields) 1277 1296 1278 def removePrinterFromGroup(self, pgroup, printer) : 1297 1279 """Removes a printer from a printer group.""" 1298 1280 try : 1299 1281 pgroup.uniqueMember.remove(printer.ident) 1300 except ValueError : 1282 except ValueError : 1301 1283 pass 1302 else : 1284 else : 1303 1285 fields = { 1304 1286 "uniqueMember" : pgroup.uniqueMember, 1305 } 1306 self.doModify(pgroup.ident, fields) 1307 1287 } 1288 self.doModify(pgroup.ident, fields) 1289 1308 1290 def retrieveHistory(self, user=None, printer=None, hostname=None, billingcode=None, jobid=None, limit=100, start=None, end=None) : 1309 1291 """Retrieves all print jobs for user on printer (or all) between start and end date, limited to first 100 results.""" … … 1320 1302 if jobid is not None : 1321 1303 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 : 1323 1305 where = "(&%s)" % "".join([precond] + where) 1324 else : 1306 else : 1325 1307 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", 1343 1325 "pykotaPrecomputedJobSize", 1344 1326 "pykotaPrecomputedJobPrice", 1345 "createTimestamp" ], 1327 "createTimestamp" ], 1346 1328 base=self.info["jobbase"]) 1347 1329 if result : … … 1353 1335 try : 1354 1336 job.JobSize = int(fields.get("pykotaJobSize", [0])[0]) 1355 except ValueError : 1337 except ValueError : 1356 1338 job.JobSize = None 1357 try : 1339 try : 1358 1340 job.JobPrice = float(fields.get("pykotaJobPrice", [0.0])[0]) 1359 1341 except ValueError : 1360 1342 job.JobPrice = None 1361 1343 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]) 1364 1346 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]) 1366 1348 job.JobHostName = fields.get("pykotaHostName", [""])[0] 1367 1349 job.JobSizeBytes = fields.get("pykotaJobSizeBytes", [0L])[0] … … 1371 1353 try : 1372 1354 job.PrecomputedJobSize = int(fields.get("pykotaPrecomputedJobSize", [0])[0]) 1373 except ValueError : 1355 except ValueError : 1374 1356 job.PrecomputedJobSize = None 1375 try : 1357 try : 1376 1358 job.PrecomputedJobPrice = float(fields.get("pykotaPrecomputedJobPrice", [0.0])[0]) 1377 1359 except ValueError : … … 1390 1372 job.Exists = True 1391 1373 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 : 1394 1376 jobs = jobs[:int(limit)] 1395 1377 return jobs 1396 1397 def deleteUser(self, user) : 1378 1379 def deleteUser(self, user) : 1398 1380 """Completely deletes an user from the Quota Storage.""" 1399 1381 uname = self.userCharsetToDatabase(user.Name) 1400 todelete = [] 1382 todelete = [] 1401 1383 result = self.doSearch("(&(objectClass=pykotaJob)(pykotaUserName=%s))" % uname, base=self.info["jobbase"]) 1402 1384 for (ident, fields) in result : … … 1412 1394 # ensure the user print quota entry will be deleted 1413 1395 todelete.append(ident) 1414 1396 1415 1397 # if last job of current printer was printed by the user 1416 1398 # to delete, we also need to delete the printer's last job entry. … … 1418 1400 if printer.LastJob.UserName == user.Name : 1419 1401 todelete.append(printer.LastJob.lastjobident) 1420 1421 for ident in todelete : 1402 1403 for ident in todelete : 1422 1404 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) 1425 1407 if result : 1426 1408 fields = result[0][1] … … 1428 1410 if k.startswith("pykota") : 1429 1411 del fields[k] 1430 elif k.lower() == "objectclass" : 1412 elif k.lower() == "objectclass" : 1431 1413 todelete = [] 1432 1414 for i in range(len(fields[k])) : 1433 if fields[k][i].startswith("pykota") : 1415 if fields[k][i].startswith("pykota") : 1434 1416 todelete.append(i) 1435 todelete.sort() 1417 todelete.sort() 1436 1418 todelete.reverse() 1437 1419 for i in todelete : 1438 1420 del fields[k][i] 1439 1421 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 : 1442 1424 self.doDelete(user.ident) 1443 1425 result = self.doSearch("(&(objectClass=pykotaAccountBalance)(pykotaUserName=%s))" % \ … … 1447 1429 for (ident, fields) in result : 1448 1430 self.doDelete(ident) 1449 1450 def deleteGroup(self, group) : 1431 1432 def deleteGroup(self, group) : 1451 1433 """Completely deletes a group from the Quota Storage.""" 1452 1434 gname = self.userCharsetToDatabase(group.Name) … … 1461 1443 for (ident, fields) in result : 1462 1444 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) 1464 1446 if result : 1465 1447 fields = result[0][1] … … 1467 1449 if k.startswith("pykota") : 1468 1450 del fields[k] 1469 elif k.lower() == "objectclass" : 1451 elif k.lower() == "objectclass" : 1470 1452 todelete = [] 1471 1453 for i in range(len(fields[k])) : 1472 if fields[k][i].startswith("pykota") : 1454 if fields[k][i].startswith("pykota") : 1473 1455 todelete.append(i) 1474 todelete.sort() 1456 todelete.sort() 1475 1457 todelete.reverse() 1476 1458 for i in todelete : 1477 1459 del fields[k][i] 1478 1460 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 : 1481 1463 self.doDelete(group.ident) 1482 1464 1483 1465 def deleteManyBillingCodes(self, billingcodes) : 1484 1466 """Deletes many billing codes.""" 1485 1467 for bcode in billingcodes : 1486 1468 bcode.delete() 1487 1488 def deleteManyUsers(self, users) : 1469 1470 def deleteManyUsers(self, users) : 1489 1471 """Deletes many users.""" 1490 1472 for user in users : 1491 1473 user.delete() 1492 1493 def deleteManyGroups(self, groups) : 1474 1475 def deleteManyGroups(self, groups) : 1494 1476 """Deletes many groups.""" 1495 1477 for group in groups : 1496 1478 group.delete() 1497 1498 def deleteManyPrinters(self, printers) : 1479 1480 def deleteManyPrinters(self, printers) : 1499 1481 """Deletes many printers.""" 1500 1482 for printer in printers : 1501 1483 printer.delete() 1502 1503 def deleteManyUserPQuotas(self, printers, users) : 1484 1485 def deleteManyUserPQuotas(self, printers, users) : 1504 1486 """Deletes many user print quota entries.""" 1505 1487 # TODO : grab all with a single (possibly VERY huge) filter if possible (might depend on the LDAP server !) … … 1509 1491 if upq.Exists : 1510 1492 upq.delete() 1511 1493 1512 1494 def deleteManyGroupPQuotas(self, printers, groups) : 1513 1495 """Deletes many group print quota entries.""" … … 1518 1500 if gpq.Exists : 1519 1501 gpq.delete() 1520 1521 def deleteUserPQuota(self, upquota) : 1502 1503 def deleteUserPQuota(self, upquota) : 1522 1504 """Completely deletes an user print quota entry from the database.""" 1523 1505 uname = self.userCharsetToDatabase(upquota.User.Name) … … 1531 1513 self.doDelete(upquota.Printer.LastJob.lastjobident) 1532 1514 self.doDelete(upquota.ident) 1533 1534 def deleteGroupPQuota(self, gpquota) : 1515 1516 def deleteGroupPQuota(self, gpquota) : 1535 1517 """Completely deletes a group print quota entry from the database.""" 1536 1518 self.doDelete(gpquota.ident) 1537 1538 def deletePrinter(self, printer) : 1519 1520 def deletePrinter(self, printer) : 1539 1521 """Completely deletes a printer from the Quota Storage.""" 1540 1522 pname = self.userCharsetToDatabase(printer.Name) … … 1559 1541 for (ident, fields) in result : 1560 1542 self.doDelete(ident) 1561 for parent in self.getParentPrinters(printer) : 1543 for parent in self.getParentPrinters(printer) : 1562 1544 try : 1563 1545 parent.uniqueMember.remove(printer.ident) 1564 except ValueError : 1546 except ValueError : 1565 1547 pass 1566 else : 1548 else : 1567 1549 fields = { 1568 1550 "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 1573 1555 def deleteBillingCode(self, code) : 1574 1556 """Deletes a billing code from the Quota Storage (no entries are deleted from the history)""" 1575 1557 self.doDelete(code.ident) 1576 1577 def sortRecords(self, fields, records, default, ordering) : 1558 1559 def sortRecords(self, fields, records, default, ordering) : 1578 1560 """Sort records based on list of fields prefixed with '+' (ASC) or '-' (DESC).""" 1579 1561 fieldindexes = {} 1580 1562 for i in range(len(fields)) : 1581 1563 fieldindexes[fields[i]] = i 1582 if not ordering : 1564 if not ordering : 1583 1565 ordering = default 1584 orderby = [] 1566 orderby = [] 1585 1567 for orderkey in ordering : 1586 1568 # Create ordering hints, ignoring unknown fields … … 1593 1575 if index is not None : 1594 1576 orderby.append((+1, index)) 1595 else : 1577 else : 1596 1578 index = fieldindexes.get(orderkey) 1597 1579 if index is not None : 1598 1580 orderby.append((+1, index)) 1599 1600 def compare(x, y, orderby=orderby) : 1581 1582 def compare(x, y, orderby=orderby) : 1601 1583 """Compares two records.""" 1602 1584 i = 0 … … 1607 1589 if not result : 1608 1590 i += 1 1609 else : 1591 else : 1610 1592 return sign * result 1611 return 0 # identical keys 1612 1593 return 0 # identical keys 1594 1613 1595 records.sort(compare) 1614 1596 return records 1615 1597 1616 1598 def extractPrinters(self, extractonly={}, ordering=[]) : 1617 1599 """Extracts all printer records.""" … … 1624 1606 if entry.PassThrough in (1, "1", "t", "true", "T", "TRUE", "True") : 1625 1607 passthrough = "t" 1626 else : 1608 else : 1627 1609 passthrough = "f" 1628 1610 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 1631 1613 def extractUsers(self, extractonly={}, ordering=[]) : 1632 1614 """Extracts all user records.""" … … 1639 1621 result.append((entry.ident, entry.Name, entry.AccountBalance, entry.LifeTimePaid, entry.LimitBy, entry.Email, entry.Description, entry.OverCharge)) 1640 1622 return [fields] + self.sortRecords(fields, result, ["+dn"], ordering) 1641 1623 1642 1624 def extractBillingcodes(self, extractonly={}, ordering=[]) : 1643 1625 """Extracts all billing codes records.""" … … 1650 1632 result.append((entry.ident, entry.BillingCode, entry.Balance, entry.PageCounter, entry.Description)) 1651 1633 return [fields] + self.sortRecords(fields, result, ["+dn"], ordering) 1652 1634 1653 1635 def extractGroups(self, extractonly={}, ordering=[]) : 1654 1636 """Extracts all group records.""" … … 1661 1643 result.append((entry.ident, entry.Name, entry.LimitBy, entry.AccountBalance, entry.LifeTimePaid, entry.Description)) 1662 1644 return [fields] + self.sortRecords(fields, result, ["+dn"], ordering) 1663 1645 1664 1646 def extractPayments(self, extractonly={}, ordering=[]) : 1665 1647 """Extracts all payment records.""" … … 1680 1662 result.append((entry.Name, amount, date, description)) 1681 1663 return [fields] + self.sortRecords(fields, result, ["+date"], ordering) 1682 1664 1683 1665 def extractUpquotas(self, extractonly={}, ordering=[]) : 1684 1666 """Extracts all userpquota records.""" … … 1693 1675 result.append((user.Name, entry.Name, userpquota.ident, user.ident, entry.ident, userpquota.LifePageCounter, userpquota.PageCounter, userpquota.SoftLimit, userpquota.HardLimit, userpquota.DateLimit)) 1694 1676 return [fields] + self.sortRecords(fields, result, ["+userdn"], ordering) 1695 1677 1696 1678 def extractGpquotas(self, extractonly={}, ordering=[]) : 1697 1679 """Extracts all grouppquota records.""" … … 1706 1688 result.append((group.Name, entry.Name, grouppquota.ident, group.ident, entry.ident, grouppquota.LifePageCounter, grouppquota.PageCounter, grouppquota.SoftLimit, grouppquota.HardLimit, grouppquota.DateLimit)) 1707 1689 return [fields] + self.sortRecords(fields, result, ["+groupdn"], ordering) 1708 1690 1709 1691 def extractUmembers(self, extractonly={}, ordering=[]) : 1710 1692 """Extracts all user groups members.""" … … 1720 1702 result.append((entry.Name, member.Name, entry.ident, member.ident)) 1721 1703 return [fields] + self.sortRecords(fields, result, ["+groupdn", "+userdn"], ordering) 1722 1704 1723 1705 def extractPmembers(self, extractonly={}, ordering=[]) : 1724 1706 """Extracts all printer groups members.""" … … 1734 1716 result.append((parent.Name, entry.Name, parent.ident, entry.ident)) 1735 1717 return [fields] + self.sortRecords(fields, result, ["+pgroupdn", "+printerdn"], ordering) 1736 1718 1737 1719 def extractHistory(self, extractonly={}, ordering=[]) : 1738 1720 """Extracts all jobhistory records.""" … … 1740 1722 if uname : 1741 1723 user = self.getUser(uname) 1742 else : 1724 else : 1743 1725 user = None 1744 1726 pname = extractonly.get("printername") 1745 1727 if pname : 1746 1728 printer = self.getPrinter(pname) 1747 else : 1729 else : 1748 1730 printer = None 1749 1731 startdate = extractonly.get("start") … … 1755 1737 result = [] 1756 1738 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)) 1758 1740 return [fields] + self.sortRecords(fields, result, ["+dn"], ordering) 1759 1741 1760 1742 def getBillingCodeFromBackend(self, label) : 1761 1743 """Extracts billing code information given its label : returns first matching billing code.""" … … 1772 1754 code.PageCounter = int(fields.get("pykotaPageCounter", [0])[0]) 1773 1755 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]) 1775 1757 code.Exists = True 1776 return code 1777 1758 return code 1759 1778 1760 def addBillingCode(self, bcode) : 1779 1761 """Adds a billing code to the quota storage, returns it.""" … … 1788 1770 "pykotaPageCounter" : str(bcode.PageCounter or 0), 1789 1771 "pykotaBalance" : str(bcode.Balance or 0.0), 1790 "description" : self.userCharsetToDatabase(bcode.Description or ""), 1791 } 1772 "description" : self.userCharsetToDatabase(bcode.Description or ""), 1773 } 1792 1774 self.doAdd(dn, fields) 1793 1775 bcode.isDirty = False 1794 1776 return None # the entry created doesn't need further modification 1795 1777 1796 1778 def saveBillingCode(self, bcode) : 1797 1779 """Sets the new description for a billing code.""" 1798 1780 fields = { 1799 "description" : self.userCharsetToDatabase(bcode.Description or ""), 1781 "description" : self.userCharsetToDatabase(bcode.Description or ""), 1800 1782 "pykotaPageCounter" : str(bcode.PageCounter or 0), 1801 1783 "pykotaBalance" : str(bcode.Balance or 0.0), 1802 1784 } 1803 1785 self.doModify(bcode.ident, fields) 1804 1786 1805 1787 def getMatchingBillingCodes(self, billingcodepattern) : 1806 1788 """Returns the list of all billing codes which match a certain pattern.""" … … 1811 1793 if result : 1812 1794 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) 1820 1796 for (codeid, fields) in result : 1821 1797 codename = self.databaseToUserCharset(fields.get("pykotaBillingCode", [""])[0]) … … 1825 1801 code.PageCounter = int(fields.get("pykotaPageCounter", [0])[0]) 1826 1802 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]) 1828 1804 code.Exists = True 1829 1805 codes.append(code) 1830 1806 self.cacheEntry("BILLINGCODES", code.BillingCode, code) 1831 return codes 1832 1807 return codes 1808 1833 1809 def consumeBillingCode(self, bcode, pagecounter, balance) : 1834 1810 """Consumes from a billing code.""" … … 1837 1813 "pykotaPageCounter" : { "operator" : "+", "value" : pagecounter, "convert" : int }, 1838 1814 } 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) : 1842 1818 """Marks a job as refunded in the history.""" 1843 1819 dn = "cn=%s,%s" % (ident, self.info["jobbase"]) 1844 1820 fields = { 1845 1821 "pykotaAction" : "REFUND", 1846 } 1847 self.doModify(dn, fields) 1848 1822 } 1823 self.doModify(dn, fields) 1824 1849 1825 def storageUserFromRecord(self, username, record) : 1850 1826 """Returns a StorageUser instance from a database record.""" … … 1852 1828 user.Exists = True 1853 1829 return user 1854 1830 1855 1831 def storageGroupFromRecord(self, groupname, record) : 1856 1832 """Returns a StorageGroup instance from a database record.""" … … 1858 1834 group.Exists = True 1859 1835 return group 1860 1836 1861 1837 def storagePrinterFromRecord(self, printername, record) : 1862 1838 """Returns a StoragePrinter instance from a database record.""" … … 1864 1840 printer.Exists = True 1865 1841 return printer 1866 1867 def setJobAttributesFromRecord(self, job, record) : 1842 1843 def setJobAttributesFromRecord(self, job, record) : 1868 1844 """Sets the attributes of a job from a database record.""" 1869 1845 job.Exists = True 1870 1846 1871 1847 def storageJobFromRecord(self, record) : 1872 1848 """Returns a StorageJob instance from a database record.""" … … 1874 1850 self.setJobAttributesFromRecord(job, record) 1875 1851 return job 1876 1852 1877 1853 def storageLastJobFromRecord(self, printer, record) : 1878 1854 """Returns a StorageLastJob instance from a database record.""" … … 1880 1856 self.setJobAttributesFromRecord(lastjob, record) 1881 1857 return lastjob 1882 1858 1883 1859 def storageUserPQuotaFromRecord(self, user, printer, record) : 1884 1860 """Returns a StorageUserPQuota instance from a database record.""" … … 1886 1862 userpquota.Exists = True 1887 1863 return userpquota 1888 1864 1889 1865 def storageGroupPQuotaFromRecord(self, group, printer, record) : 1890 1866 """Returns a StorageGroupPQuota instance from a database record.""" … … 1892 1868 grouppquota.Exists = True 1893 1869 return grouppquota 1894 1870 1895 1871 def storageBillingCodeFromRecord(self, billingcode, record) : 1896 1872 """Returns a StorageBillingCode instance from a database record.""" -
pykota/branches/1.26_fixes/pykota/storages/sql.py
r3393 r3522 14 14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 15 # GNU General Public License for more details. 16 # 16 # 17 17 # You should have received a copy of the GNU General Public License 18 18 # along with this program; if not, write to the Free Software … … 28 28 StorageJob, StorageLastJob, StorageUserPQuota, \ 29 29 StorageGroupPQuota, StorageBillingCode 30 31 MAXINNAMES = 500 # Maximum number of non-patterns names to use in a single IN statement 30 32 31 33 class SQLStorage : … … 42 44 user.Exists = True 43 45 return user 44 46 45 47 def storageGroupFromRecord(self, groupname, record) : 46 48 """Returns a StorageGroup instance from a database record.""" … … 53 55 group.Exists = True 54 56 return group 55 57 56 58 def storagePrinterFromRecord(self, printername, record) : 57 59 """Returns a StoragePrinter instance from a database record.""" … … 69 71 printer.Exists = True 70 72 return printer 71 72 def setJobAttributesFromRecord(self, job, record) : 73 74 def setJobAttributesFromRecord(self, job, record) : 73 75 """Sets the attributes of a job from a database record.""" 74 76 job.ident = record.get("id") … … 78 80 job.JobPrice = record.get("jobprice") 79 81 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 "") 82 84 job.JobCopies = record.get("copies") 83 job.JobOptions = self.databaseToUserCharset(record.get("options") or "") 85 job.JobOptions = self.databaseToUserCharset(record.get("options") or "") 84 86 job.JobDate = record.get("jobdate") 85 87 job.JobHostName = record.get("hostname") … … 95 97 (job.JobTitle, job.JobFileName, job.JobOptions) = (_("Hidden because of privacy concerns"),) * 3 96 98 job.Exists = True 97 99 98 100 def storageJobFromRecord(self, record) : 99 101 """Returns a StorageJob instance from a database record.""" … … 101 103 self.setJobAttributesFromRecord(job, record) 102 104 return job 103 105 104 106 def storageLastJobFromRecord(self, printer, record) : 105 107 """Returns a StorageLastJob instance from a database record.""" … … 107 109 self.setJobAttributesFromRecord(lastjob, record) 108 110 return lastjob 109 111 110 112 def storageUserPQuotaFromRecord(self, user, printer, record) : 111 113 """Returns a StorageUserPQuota instance from a database record.""" … … 120 122 userpquota.Exists = True 121 123 return userpquota 122 124 123 125 def storageGroupPQuotaFromRecord(self, group, printer, record) : 124 126 """Returns a StorageGroupPQuota instance from a database record.""" … … 135 137 grouppquota.Exists = True 136 138 return grouppquota 137 139 138 140 def storageBillingCodeFromRecord(self, billingcode, record) : 139 141 """Returns a StorageBillingCode instance from a database record.""" … … 145 147 code.Exists = True 146 148 return code 147 148 def createFilter(self, only) : 149 150 def createFilter(self, only) : 149 151 """Returns the appropriate SQL filter.""" 150 152 if only : … … 152 154 for (k, v) in only.items() : 153 155 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) : 158 160 """Creates a suitable ORDER BY statement based on a list of fieldnames prefixed with '+' (ASC) or '-' (DESC).""" 159 161 statements = [] 160 162 if not ordering : 161 163 ordering = default 162 for field in ordering : 163 if field.startswith("-") : 164 for field in ordering : 165 if field.startswith("-") : 164 166 statements.append("%s DESC" % field[1:]) 165 167 elif field.startswith("+") : 166 168 statements.append("%s ASC" % field[1:]) 167 else : 169 else : 168 170 statements.append("%s ASC" % field) 169 return ", ".join(statements) 170 171 return ", ".join(statements) 172 171 173 def extractPrinters(self, extractonly={}, ordering=[]) : 172 174 """Extracts all printer records.""" … … 177 179 result = self.doRawSearch("SELECT * FROM printers %(thefilter)s ORDER BY %(orderby)s" % locals()) 178 180 return self.prepareRawResult(result) 179 181 180 182 def extractUsers(self, extractonly={}, ordering=[]) : 181 183 """Extracts all user records.""" … … 186 188 result = self.doRawSearch("SELECT * FROM users %(thefilter)s ORDER BY %(orderby)s" % locals()) 187 189 return self.prepareRawResult(result) 188 190 189 191 def extractBillingcodes(self, extractonly={}, ordering=[]) : 190 192 """Extracts all billing codes records.""" … … 195 197 result = self.doRawSearch("SELECT * FROM billingcodes %(thefilter)s ORDER BY %(orderby)s" % locals()) 196 198 return self.prepareRawResult(result) 197 199 198 200 def extractGroups(self, extractonly={}, ordering=[]) : 199 201 """Extracts all group records.""" … … 204 206 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()) 205 207 return self.prepareRawResult(result) 206 208 207 209 def extractPayments(self, extractonly={}, ordering=[]) : 208 210 """Extracts all payment records.""" … … 212 214 try : 213 215 del extractonly[limit] 214 except KeyError : 216 except KeyError : 215 217 pass 216 218 thefilter = self.createFilter(extractonly) … … 218 220 thefilter = "AND %s" % thefilter 219 221 (startdate, enddate) = self.cleanDates(startdate, enddate) 220 if startdate : 222 if startdate : 221 223 thefilter = "%s AND date>=%s" % (thefilter, self.doQuote(startdate)) 222 if enddate : 224 if enddate : 223 225 thefilter = "%s AND date<=%s" % (thefilter, self.doQuote(enddate)) 224 226 orderby = self.createOrderBy(["+payments.id"], ordering) 225 227 result = self.doRawSearch("SELECT username,payments.* FROM users,payments WHERE users.id=payments.userid %(thefilter)s ORDER BY %(orderby)s" % locals()) 226 228 return self.prepareRawResult(result) 227 229 228 230 def extractUpquotas(self, extractonly={}, ordering=[]) : 229 231 """Extracts all userpquota records.""" … … 234 236 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()) 235 237 return self.prepareRawResult(result) 236 238 237 239 def extractGpquotas(self, extractonly={}, ordering=[]) : 238 240 """Extracts all grouppquota records.""" … … 243 245 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()) 244 246 return self.prepareRawResult(result) 245 247 246 248 def extractUmembers(self, extractonly={}, ordering=[]) : 247 249 """Extracts all user groups members.""" … … 252 254 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()) 253 255 return self.prepareRawResult(result) 254 256 255 257 def extractPmembers(self, extractonly={}, ordering=[]) : 256 258 """Extracts all printer groups members.""" … … 268 270 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()) 269 271 return self.prepareRawResult(result) 270 272 271 273 def extractHistory(self, extractonly={}, ordering=[]) : 272 274 """Extracts all jobhistory records.""" … … 276 278 try : 277 279 del extractonly[limit] 278 except KeyError : 280 except KeyError : 279 281 pass 280 282 thefilter = self.createFilter(extractonly) … … 282 284 thefilter = "AND %s" % thefilter 283 285 (startdate, enddate) = self.cleanDates(startdate, enddate) 284 if startdate : 286 if startdate : 285 287 thefilter = "%s AND jobdate>=%s" % (thefilter, self.doQuote(startdate)) 286 if enddate : 288 if enddate : 287 289 thefilter = "%s AND jobdate<=%s" % (thefilter, self.doQuote(enddate)) 288 290 orderby = self.createOrderBy(["+jobhistory.id"], ordering) 289 291 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()) 290 292 return self.prepareRawResult(result) 291 293 292 294 def filterNames(self, records, attribute, patterns=None) : 293 295 """Returns a list of 'attribute' from a list of records. 294 296 295 297 Logs any missing attribute. 296 """ 298 """ 297 299 result = [] 298 300 for record in records : … … 307 309 if self.tool.matchString(attrval, patterns) : 308 310 result.append(attrval) 309 else : 311 else : 310 312 result.append(attrval) 311 return result 312 313 def getAllBillingCodes(self, billingcode=None) : 313 return result 314 315 def getAllBillingCodes(self, billingcode=None) : 314 316 """Extracts all billing codes or only the billing codes matching the optional parameter.""" 315 317 result = self.doSearch("SELECT billingcode FROM billingcodes") 316 318 if result : 317 319 return self.filterNames(result, "billingcode", billingcode) 318 else : 320 else : 319 321 return [] 320 321 def getAllPrintersNames(self, printername=None) : 322 323 def getAllPrintersNames(self, printername=None) : 322 324 """Extracts all printer names or only the printers' names matching the optional parameter.""" 323 325 result = self.doSearch("SELECT printername FROM printers") 324 326 if result : 325 327 return self.filterNames(result, "printername", printername) 326 else : 328 else : 327 329 return [] 328 329 def getAllUsersNames(self, username=None) : 330 331 def getAllUsersNames(self, username=None) : 330 332 """Extracts all user names.""" 331 333 result = self.doSearch("SELECT username FROM users") 332 334 if result : 333 335 return self.filterNames(result, "username", username) 334 else : 336 else : 335 337 return [] 336 337 def getAllGroupsNames(self, groupname=None) : 338 339 def getAllGroupsNames(self, groupname=None) : 338 340 """Extracts all group names.""" 339 341 result = self.doSearch("SELECT groupname FROM groups") … … 342 344 else : 343 345 return [] 344 346 345 347 def getUserNbJobsFromHistory(self, user) : 346 348 """Returns the number of jobs the user has in history.""" … … 349 351 return result[0]["count"] 350 352 return 0 351 352 def getUserFromBackend(self, username) : 353 354 def getUserFromBackend(self, username) : 353 355 """Extracts user information given its name.""" 354 356 result = self.doSearch("SELECT * FROM users WHERE username=%s LIMIT 1"\ … … 356 358 if result : 357 359 return self.storageUserFromRecord(username, result[0]) 358 else : 360 else : 359 361 return StorageUser(self, username) 360 361 def getGroupFromBackend(self, groupname) : 362 363 def getGroupFromBackend(self, groupname) : 362 364 """Extracts group information given its name.""" 363 365 result = self.doSearch("SELECT groups.*,COALESCE(SUM(balance), 0.0) AS balance, COALESCE(SUM(lifetimepaid), 0.0) AS lifetimepaid FROM groups LEFT OUTER JOIN users ON users.id IN (SELECT userid FROM groupsmembers WHERE groupid=groups.id) WHERE groupname=%s GROUP BY groups.id,groups.groupname,groups.limitby,groups.description LIMIT 1" \ … … 365 367 if result : 366 368 return self.storageGroupFromRecord(groupname, result[0]) 367 else : 369 else : 368 370 return StorageGroup(self, groupname) 369 370 def getPrinterFromBackend(self, printername) : 371 372 def getPrinterFromBackend(self, printername) : 371 373 """Extracts printer information given its name.""" 372 374 result = self.doSearch("SELECT * FROM printers WHERE printername=%s LIMIT 1" \ … … 374 376 if result : 375 377 return self.storagePrinterFromRecord(printername, result[0]) 376 else : 378 else : 377 379 return StoragePrinter(self, printername) 378 379 def getBillingCodeFromBackend(self, label) : 380 381 def getBillingCodeFromBackend(self, label) : 380 382 """Extracts a billing code information given its name.""" 381 383 result = self.doSearch("SELECT * FROM billingcodes WHERE billingcode=%s LIMIT 1" \ … … 383 385 if result : 384 386 return self.storageBillingCodeFromRecord(label, result[0]) 385 else : 387 else : 386 388 return StorageBillingCode(self, label) 387 388 def getUserPQuotaFromBackend(self, user, printer) : 389 390 def getUserPQuotaFromBackend(self, user, printer) : 389 391 """Extracts a user print quota.""" 390 392 if printer.Exists and user.Exists : … … 394 396 return self.storageUserPQuotaFromRecord(user, printer, result[0]) 395 397 return StorageUserPQuota(self, user, printer) 396 397 def getGroupPQuotaFromBackend(self, group, printer) : 398 399 def getGroupPQuotaFromBackend(self, group, printer) : 398 400 """Extracts a group print quota.""" 399 401 if printer.Exists and group.Exists : … … 403 405 return self.storageGroupPQuotaFromRecord(group, printer, result[0]) 404 406 return StorageGroupPQuota(self, group, printer) 405 406 def getPrinterLastJobFromBackend(self, printer) : 407 408 def getPrinterLastJobFromBackend(self, printer) : 407 409 """Extracts a printer's last job information.""" 408 410 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)) 409 411 if result : 410 412 return self.storageLastJobFromRecord(printer, result[0]) 411 else : 413 else : 412 414 return StorageLastJob(self, printer) 413 414 def getGroupMembersFromBackend(self, group) : 415 416 def getGroupMembersFromBackend(self, group) : 415 417 """Returns the group's members list.""" 416 418 groupmembers = [] … … 422 424 groupmembers.append(user) 423 425 self.cacheEntry("USERS", user.Name, user) 424 return groupmembers 425 426 def getUserGroupsFromBackend(self, user) : 426 return groupmembers 427 428 def getUserGroupsFromBackend(self, user) : 427 429 """Returns the user's groups list.""" 428 430 groups = [] … … 431 433 for record in result : 432 434 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) : 436 438 """Get all the printer groups this printer is a member of.""" 437 439 pgroups = [] … … 444 446 pgroups.append(parentprinter) 445 447 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 447 457 def getMatchingPrinters(self, printerpattern) : 448 458 """Returns the list of all printers for which name matches a certain pattern.""" … … 454 464 if result : 455 465 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) 463 467 for record in result : 464 468 pname = self.databaseToUserCharset(record["printername"]) … … 467 471 printers.append(printer) 468 472 self.cacheEntry("PRINTERS", printer.Name, printer) 469 return printers 470 473 return printers 474 471 475 def getMatchingUsers(self, userpattern) : 472 476 """Returns the list of all users for which name matches a certain pattern.""" … … 475 479 # but we don't because other storages semantics may be different, so every 476 480 # 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 495 518 def getMatchingGroups(self, grouppattern) : 496 519 """Returns the list of all groups for which name matches a certain pattern.""" … … 502 525 if result : 503 526 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) 511 528 for record in result : 512 529 gname = self.databaseToUserCharset(record["groupname"]) … … 515 532 groups.append(group) 516 533 self.cacheEntry("GROUPS", group.Name, group) 517 return groups 518 534 return groups 535 519 536 def getMatchingBillingCodes(self, billingcodepattern) : 520 537 """Returns the list of all billing codes for which the label matches a certain pattern.""" … … 523 540 if result : 524 541 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) 532 543 for record in result : 533 544 codename = self.databaseToUserCharset(record["billingcode"]) … … 536 547 codes.append(code) 537 548 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=["*"]) : 541 552 """Returns the list of users who uses a given printer, along with their quotas.""" 542 553 usersandquotas = [] … … 552 563 self.cacheEntry("USERPQUOTAS", "%s@%s" % (user.Name, printer.Name), userpquota) 553 564 return usersandquotas 554 555 def getPrinterGroupsAndQuotas(self, printer, names=["*"]) : 565 566 def getPrinterGroupsAndQuotas(self, printer, names=["*"]) : 556 567 """Returns the list of groups which uses a given printer, along with their quotas.""" 557 568 groupsandquotas = [] … … 565 576 groupsandquotas.append((group, grouppquota)) 566 577 return groupsandquotas 567 568 def addPrinter(self, printer) : 578 579 def addPrinter(self, printer) : 569 580 """Adds a printer to the quota storage, returns the old value if it already exists.""" 570 581 oldentry = self.getPrinter(printer.Name) … … 580 591 printer.isDirty = False 581 592 return None # the entry created doesn't need further modification 582 593 583 594 def addBillingCode(self, bcode) : 584 595 """Adds a billing code to the quota storage, returns the old value if it already exists.""" … … 587 598 return oldentry 588 599 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)), 590 601 self.doQuote(bcode.Balance or 0.0), \ 591 602 self.doQuote(bcode.PageCounter or 0), \ … … 593 604 bcode.isDirty = False 594 605 return None # the entry created doesn't need further modification 595 596 def addUser(self, user) : 606 607 def addUser(self, user) : 597 608 """Adds a user to the quota storage, returns the old value if it already exists.""" 598 609 oldentry = self.getUser(user.Name) … … 613 624 user.isDirty = False 614 625 return None # the entry created doesn't need further modification 615 616 def addGroup(self, group) : 626 627 def addGroup(self, group) : 617 628 """Adds a group to the quota storage, returns the old value if it already exists.""" 618 629 oldentry = self.getGroup(group.Name) … … 626 637 return None # the entry created doesn't need further modification 627 638 628 def addUserToGroup(self, user, group) : 639 def addUserToGroup(self, user, group) : 629 640 """Adds an user to a group.""" 630 641 result = self.doSearch("SELECT COUNT(*) AS mexists FROM groupsmembers WHERE groupid=%s AND userid=%s" % (self.doQuote(group.ident), self.doQuote(user.ident))) 631 642 try : 632 643 mexists = int(result[0].get("mexists")) 633 except (IndexError, TypeError) : 644 except (IndexError, TypeError) : 634 645 mexists = 0 635 if not mexists : 646 if not mexists : 636 647 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) : 639 650 """Removes an user from a group.""" 640 651 self.doModify("DELETE FROM groupsmembers WHERE groupid=%s AND userid=%s" % \ 641 652 (self.doQuote(group.ident), self.doQuote(user.ident))) 642 653 643 654 def addUserPQuota(self, upq) : 644 655 """Initializes a user print quota on a printer.""" … … 658 669 upq.isDirty = False 659 670 return None # the entry created doesn't need further modification 660 671 661 672 def addGroupPQuota(self, gpq) : 662 673 """Initializes a group print quota on a printer.""" … … 673 684 gpq.isDirty = False 674 685 return None # the entry created doesn't need further modification 675 676 def savePrinter(self, printer) : 686 687 def savePrinter(self, printer) : 677 688 """Saves the printer to the database in a single operation.""" 678 689 self.doModify("UPDATE printers SET passthrough=%s, maxjobsize=%s, description=%s, priceperpage=%s, priceperjob=%s WHERE id=%s" \ … … 683 694 self.doQuote(printer.PricePerJob or 0.0), \ 684 695 self.doQuote(printer.ident))) 685 686 def saveUser(self, user) : 696 697 def saveUser(self, user) : 687 698 """Saves the user to the database in a single operation.""" 688 699 self.doModify("UPDATE users SET limitby=%s, balance=%s, lifetimepaid=%s, email=%s, overcharge=%s, description=%s WHERE id=%s" \ … … 694 705 self.doQuote(self.userCharsetToDatabase(user.Description)), \ 695 706 self.doQuote(user.ident))) 696 697 def saveGroup(self, group) : 707 708 def saveGroup(self, group) : 698 709 """Saves the group to the database in a single operation.""" 699 710 self.doModify("UPDATE groups SET limitby=%s, description=%s WHERE id=%s" \ … … 701 712 self.doQuote(self.userCharsetToDatabase(group.Description)), \ 702 713 self.doQuote(group.ident))) 703 704 def writeUserPQuotaDateLimit(self, userpquota, datelimit) : 714 715 def writeUserPQuotaDateLimit(self, userpquota, datelimit) : 705 716 """Sets the date limit permanently for a user print quota.""" 706 717 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) : 709 720 """Sets the date limit permanently for a group print quota.""" 710 721 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) : 713 724 """Increase page counters for a user print quota.""" 714 725 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) : 717 728 """Saves the billing code to the database.""" 718 729 self.doModify("UPDATE billingcodes SET balance=%s, pagecounter=%s, description=%s WHERE id=%s" \ … … 721 732 self.doQuote(self.userCharsetToDatabase(bcode.Description)), \ 722 733 self.doQuote(bcode.ident))) 723 734 724 735 def consumeBillingCode(self, bcode, pagecounter, balance) : 725 736 """Consumes from a billing code.""" 726 737 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) : 729 740 """Marks a job as refunded in the history.""" 730 741 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) : 733 744 """Decreases user's account balance from an amount.""" 734 745 self.doModify("UPDATE users SET balance=balance - %s WHERE id=%s" % (self.doQuote(amount), self.doQuote(user.ident))) 735 746 736 747 def writeNewPayment(self, user, amount, comment="") : 737 748 """Adds a new payment to the payments history.""" 738 749 if user.ident is not None : 739 750 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 : 741 752 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) : 744 755 """Sets the last job's size permanently.""" 745 756 self.doModify("UPDATE jobhistory SET jobsize=%s, jobprice=%s WHERE id=%s" % (self.doQuote(jobsize), self.doQuote(jobprice), self.doQuote(lastjob.ident))) 746 757 747 758 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) : 748 759 """Adds a job in a printer's history.""" 749 if self.privacy : 760 if self.privacy : 750 761 # For legal reasons, we want to hide the title, filename and options 751 762 title = filename = options = "hidden" … … 757 768 if jobsize is not None : 758 769 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 : 760 771 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 : 762 773 # here we explicitly want to reset jobsize to NULL if needed 763 774 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 765 776 def saveUserPQuota(self, userpquota) : 766 777 """Saves an user print quota entry.""" … … 774 785 self.doQuote(userpquota.MaxJobSize), \ 775 786 self.doQuote(userpquota.ident))) 776 787 777 788 def writeUserPQuotaWarnCount(self, userpquota, warncount) : 778 789 """Sets the warn counter value for a user quota.""" 779 790 self.doModify("UPDATE userpquota SET warncount=%s WHERE id=%s" % (self.doQuote(warncount), self.doQuote(userpquota.ident))) 780 791 781 792 def increaseUserPQuotaWarnCount(self, userpquota) : 782 793 """Increases the warn counter value for a user quota.""" 783 794 self.doModify("UPDATE userpquota SET warncount=warncount+1 WHERE id=%s" % self.doQuote(userpquota.ident)) 784 795 785 796 def saveGroupPQuota(self, grouppquota) : 786 797 """Saves a group print quota entry.""" … … 798 809 for record in result : 799 810 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 : 801 812 self.doModify("INSERT INTO printergroupsmembers (groupid, printerid) VALUES (%s, %s)" % (self.doQuote(pgroup.ident), self.doQuote(printer.ident))) 802 813 803 814 def removePrinterFromGroup(self, pgroup, printer) : 804 815 """Removes a printer from a printer group.""" 805 816 self.doModify("DELETE FROM printergroupsmembers WHERE groupid=%s AND printerid=%s" % (self.doQuote(pgroup.ident), self.doQuote(printer.ident))) 806 817 807 818 def retrieveHistory(self, user=None, printer=None, hostname=None, billingcode=None, jobid=None, limit=100, start=None, end=None) : 808 819 """Retrieves all print jobs for user on printer (or all) between start and end date, limited to first 100 results.""" … … 813 824 if printer is not None : # printer.ident is None anyway if printer doesn't exist 814 825 where.append("printerid=%s" % self.doQuote(printer.ident)) 815 if hostname is not None : 826 if hostname is not None : 816 827 where.append("hostname=%s" % self.doQuote(hostname)) 817 if billingcode is not None : 828 if billingcode is not None : 818 829 where.append("billingcode=%s" % self.doQuote(self.userCharsetToDatabase(billingcode))) 819 if jobid is not None : 830 if jobid is not None : 820 831 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 : 822 833 where.append("jobdate>=%s" % self.doQuote(start)) 823 if end is not None : 834 if end is not None : 824 835 where.append("jobdate<=%s" % self.doQuote(end)) 825 if where : 836 if where : 826 837 query += " AND %s" % " AND ".join(where) 827 838 query += " ORDER BY jobhistory.id DESC" 828 839 if limit : 829 840 query += " LIMIT %s" % self.doQuote(int(limit)) 830 jobs = [] 831 result = self.doSearch(query) 841 jobs = [] 842 result = self.doSearch(query) 832 843 if result : 833 844 for fields in result : … … 835 846 jobs.append(job) 836 847 return jobs 837 838 def deleteUser(self, user) : 848 849 def deleteUser(self, user) : 839 850 """Completely deletes an user from the database.""" 840 851 # TODO : What should we do if we delete the last person who used a given printer ? 841 852 # TODO : we can't reassign the last job to the previous one, because next user would be 842 853 # TODO : incorrectly charged (overcharged). 843 for q in [ 854 for q in [ 844 855 "DELETE FROM payments WHERE userid=%s" % self.doQuote(user.ident), 845 856 "DELETE FROM groupsmembers WHERE userid=%s" % self.doQuote(user.ident), … … 849 860 ] : 850 861 self.doModify(q) 851 852 def multipleQueriesInTransaction(self, queries) : 862 863 def multipleQueriesInTransaction(self, queries) : 853 864 """Does many modifications in a single transaction.""" 854 865 self.beginTransaction() … … 856 867 for q in queries : 857 868 self.doModify(q) 858 except : 869 except : 859 870 self.rollbackTransaction() 860 871 raise 861 else : 872 else : 862 873 self.commitTransaction() 863 864 def deleteManyBillingCodes(self, billingcodes) : 874 875 def deleteManyBillingCodes(self, billingcodes) : 865 876 """Deletes many billing codes.""" 866 877 codeids = ", ".join(["%s" % self.doQuote(b.ident) for b in billingcodes]) 867 878 if codeids : 868 self.multipleQueriesInTransaction([ 879 self.multipleQueriesInTransaction([ 869 880 "DELETE FROM billingcodes WHERE id IN (%s)" % codeids,]) 870 871 def deleteManyUsers(self, users) : 881 882 def deleteManyUsers(self, users) : 872 883 """Deletes many users.""" 873 884 userids = ", ".join(["%s" % self.doQuote(u.ident) for u in users]) 874 885 if userids : 875 self.multipleQueriesInTransaction([ 886 self.multipleQueriesInTransaction([ 876 887 "DELETE FROM payments WHERE userid IN (%s)" % userids, 877 888 "DELETE FROM groupsmembers WHERE userid IN (%s)" % userids, … … 879 890 "DELETE FROM userpquota WHERE userid IN (%s)" % userids, 880 891 "DELETE FROM users WHERE id IN (%s)" % userids,]) 881 882 def deleteManyGroups(self, groups) : 892 893 def deleteManyGroups(self, groups) : 883 894 """Deletes many groups.""" 884 895 groupids = ", ".join(["%s" % self.doQuote(g.ident) for g in groups]) 885 896 if groupids : 886 self.multipleQueriesInTransaction([ 897 self.multipleQueriesInTransaction([ 887 898 "DELETE FROM groupsmembers WHERE groupid IN (%s)" % groupids, 888 899 "DELETE FROM grouppquota WHERE groupid IN (%s)" % groupids, 889 900 "DELETE FROM groups WHERE id IN (%s)" % groupids,]) 890 901 891 902 def deleteManyPrinters(self, printers) : 892 903 """Deletes many printers.""" 893 904 printerids = ", ".join(["%s" % self.doQuote(p.ident) for p in printers]) 894 905 if printerids : 895 self.multipleQueriesInTransaction([ 906 self.multipleQueriesInTransaction([ 896 907 "DELETE FROM printergroupsmembers WHERE groupid IN (%s) OR printerid IN (%s)" % (printerids, printerids), 897 908 "DELETE FROM jobhistory WHERE printerid IN (%s)" % printerids, … … 899 910 "DELETE FROM userpquota WHERE printerid IN (%s)" % printerids, 900 911 "DELETE FROM printers WHERE id IN (%s)" % printerids,]) 901 902 def deleteManyUserPQuotas(self, printers, users) : 912 913 def deleteManyUserPQuotas(self, printers, users) : 903 914 """Deletes many user print quota entries.""" 904 915 printerids = ", ".join(["%s" % self.doQuote(p.ident) for p in printers]) 905 916 userids = ", ".join(["%s" % self.doQuote(u.ident) for u in users]) 906 917 if userids and printerids : 907 self.multipleQueriesInTransaction([ 918 self.multipleQueriesInTransaction([ 908 919 "DELETE FROM jobhistory WHERE userid IN (%s) AND printerid IN (%s)" \ 909 920 % (userids, printerids), 910 921 "DELETE FROM userpquota WHERE userid IN (%s) AND printerid IN (%s)" \ 911 922 % (userids, printerids),]) 912 923 913 924 def deleteManyGroupPQuotas(self, printers, groups) : 914 925 """Deletes many group print quota entries.""" … … 916 927 groupids = ", ".join(["%s" % self.doQuote(g.ident) for g in groups]) 917 928 if groupids and printerids : 918 self.multipleQueriesInTransaction([ 929 self.multipleQueriesInTransaction([ 919 930 "DELETE FROM grouppquota WHERE groupid IN (%s) AND printerid IN (%s)" \ 920 931 % (groupids, printerids),]) 921 922 def deleteUserPQuota(self, upquota) : 932 933 def deleteUserPQuota(self, upquota) : 923 934 """Completely deletes an user print quota entry from the database.""" 924 for q in [ 935 for q in [ 925 936 "DELETE FROM jobhistory WHERE userid=%s AND printerid=%s" \ 926 937 % (self.doQuote(upquota.User.ident), self.doQuote(upquota.Printer.ident)), … … 928 939 ] : 929 940 self.doModify(q) 930 931 def deleteGroupPQuota(self, gpquota) : 941 942 def deleteGroupPQuota(self, gpquota) : 932 943 """Completely deletes a group print quota entry from the database.""" 933 for q in [ 944 for q in [ 934 945 "DELETE FROM grouppquota WHERE id=%s" % self.doQuote(gpquota.ident), 935 946 ] : 936 947 self.doModify(q) 937 938 def deleteGroup(self, group) : 948 949 def deleteGroup(self, group) : 939 950 """Completely deletes a group from the database.""" 940 951 for q in [ … … 942 953 "DELETE FROM grouppquota WHERE groupid=%s" % self.doQuote(group.ident), 943 954 "DELETE FROM groups WHERE id=%s" % self.doQuote(group.ident), 944 ] : 955 ] : 945 956 self.doModify(q) 946 947 def deletePrinter(self, printer) : 957 958 def deletePrinter(self, printer) : 948 959 """Completely deletes a printer from the database.""" 949 for q in [ 960 for q in [ 950 961 "DELETE FROM printergroupsmembers WHERE groupid=%s OR printerid=%s" % (self.doQuote(printer.ident), self.doQuote(printer.ident)), 951 962 "DELETE FROM jobhistory WHERE printerid=%s" % self.doQuote(printer.ident), … … 955 966 ] : 956 967 self.doModify(q) 957 958 def deleteBillingCode(self, code) : 968 969 def deleteBillingCode(self, code) : 959 970 """Completely deletes a billing code from the database.""" 960 971 for q in [ 961 972 "DELETE FROM billingcodes WHERE id=%s" % self.doQuote(code.ident), 962 ] : 973 ] : 963 974 self.doModify(q) 964 975