Index: pykota/trunk/pykota/tool.py
===================================================================
--- pykota/trunk/pykota/tool.py (revision 1229)
+++ pykota/trunk/pykota/tool.py (revision 1240)
@@ -22,4 +22,10 @@
 #
 # $Log$
+# Revision 1.65  2003/12/27 16:49:25  uid67467
+# Should be ok now.
+#
+# Revision 1.65  2003/12/06 08:14:38  jalet
+# Added support for CUPS device uris which contain authentication information.
+#
 # Revision 1.64  2003/11/29 22:03:17  jalet
 # Some code refactoring work. New code is not used at this time.
@@ -288,5 +294,5 @@
         self.documentation = doc
         self.config = config.PyKotaConfig("/etc/pykota")
-        self.logger = logger.openLogger(self, self.config.getLoggingBackend())
+        self.logger = logger.openLogger(self.config.getLoggingBackend())
         self.debug = self.config.getDebug()
         self.storage = storage.openConnection(self)
@@ -574,7 +580,8 @@
                 self.sendMessageToAdmin(adminmail, _("Print Quota"), adminmessage)
             if mailto in [ "BOTH", "USER", "EXTERNAL" ] :
+                message = self.config.getHardWarn(printer.Name)
                 for user in self.storage.getGroupMembers(group) :
                     if mailto != "EXTERNAL" :
-                        self.sendMessageToUser(admin, adminmail, user, _("Print Quota Exceeded"), self.config.getHardWarn(printer.Name))
+                        self.sendMessageToUser(admin, adminmail, user, _("Print Quota Exceeded"), message)
                     else :    
                         self.externalMailTo(arguments, action, user, printer, message)
@@ -599,37 +606,50 @@
         """Checks a user quota and send him a message if quota is exceeded on current printer."""
         user = userpquota.User
-        printer = userpquota.Printer
-        admin = self.config.getAdmin(printer.Name)
-        adminmail = self.config.getAdminMail(printer.Name)
-        (mailto, arguments) = self.config.getMailTo(printer.Name)
-        action = self.checkUserPQuota(userpquota)
-        if action.startswith("POLICY_") :
-            action = action[7:]
-        if action == "DENY" :
-            adminmessage = _("Print Quota exceeded for user %s on printer %s") % (user.Name, printer.Name)
-            self.logger.log_message(adminmessage)
-            if mailto in [ "BOTH", "USER", "EXTERNAL" ] :
-                message = self.config.getHardWarn(printer.Name)
-                if mailto != "EXTERNAL" :
-                    self.sendMessageToUser(admin, adminmail, user, _("Print Quota Exceeded"), message)
-                else :    
-                    self.externalMailTo(arguments, action, user, printer, message)
-            if mailto in [ "BOTH", "ADMIN" ] :
-                self.sendMessageToAdmin(adminmail, _("Print Quota"), adminmessage)
-        elif action == "WARN" :    
-            adminmessage = _("Print Quota low for user %s on printer %s") % (user.Name, printer.Name)
-            self.logger.log_message(adminmessage)
-            if mailto in [ "BOTH", "USER", "EXTERNAL" ] :
-                if user.LimitBy and (user.LimitBy.lower() == "balance") : 
-                    message = self.config.getPoorWarn()
-                else :     
-                    message = self.config.getSoftWarn(printer.Name)
-                if mailto != "EXTERNAL" :    
-                    self.sendMessageToUser(admin, adminmail, user, _("Print Quota Low"), message)
-                else :    
-                    self.externalMailTo(arguments, action, user, printer, message)
-            if mailto in [ "BOTH", "ADMIN" ] :
-                self.sendMessageToAdmin(adminmail, _("Print Quota"), adminmessage)
-        return action        
+        actions = []
+        for upq in [userpquota] + userpquota.ParentPrintersUserPQuota :
+            printer = upq.Printer
+            admin = self.config.getAdmin(printer.Name)
+            adminmail = self.config.getAdminMail(printer.Name)
+            (mailto, arguments) = self.config.getMailTo(printer.Name)
+            self.logdebug("Checking quota for user %s on printer %s" % (upq.User.Name, printer.Name))
+            action = self.checkUserPQuota(upq)
+            self.logdebug("Result is %s" % action)
+            if action.startswith("POLICY_") :
+                action = action[7:]
+            if action == "DENY" :
+                adminmessage = _("Print Quota exceeded for user %s on printer %s") % (user.Name, printer.Name)
+                self.logger.log_message(adminmessage)
+                if mailto in [ "BOTH", "USER", "EXTERNAL" ] :
+                    message = self.config.getHardWarn(printer.Name)
+                    if mailto != "EXTERNAL" :
+                        self.sendMessageToUser(admin, adminmail, user, _("Print Quota Exceeded"), message)
+                    else :    
+                        self.externalMailTo(arguments, action, user, printer, message)
+                if mailto in [ "BOTH", "ADMIN" ] :
+                    self.sendMessageToAdmin(adminmail, _("Print Quota"), adminmessage)
+            elif action == "WARN" :    
+                adminmessage = _("Print Quota low for user %s on printer %s") % (user.Name, printer.Name)
+                self.logger.log_message(adminmessage)
+                if mailto in [ "BOTH", "USER", "EXTERNAL" ] :
+                    if user.LimitBy and (user.LimitBy.lower() == "balance") : 
+                        message = self.config.getPoorWarn()
+                    else :     
+                        message = self.config.getSoftWarn(printer.Name)
+                    if mailto != "EXTERNAL" :    
+                        self.sendMessageToUser(admin, adminmail, user, _("Print Quota Low"), message)
+                    else :    
+                        self.externalMailTo(arguments, action, user, printer, message)
+                if mailto in [ "BOTH", "ADMIN" ] :
+                    self.sendMessageToAdmin(adminmail, _("Print Quota"), adminmessage)
+            actions.append(action)        
+        if "DENY" in actions :    
+            self.logdebug("Final result is %s" % action)
+            return "DENY"
+        elif "WARN" in actions :    
+            self.logdebug("Final result is %s" % action)
+            return "WARN"
+        else :    
+            self.logdebug("Final result is %s" % action)
+            return "ALLOW"
         
 class PyKotaFilterOrBackend(PyKotaTool) :    
@@ -651,4 +671,14 @@
             self.username = self.username.lower()
         self.preserveinputfile = self.inputfile 
+        #
+        # Export internal data to the environment
+        os.environ["PYKOTAUSERNAME"] = str(self.username)
+        os.environ["PYKOTAPRINTERNAME"] = str(self.printername)
+        os.environ["PYKOTACOPIES"] = str(self.copies)
+        os.environ["PYKOTATITLE"] = str(self.title)
+        os.environ["PYKOTAOPTIONS"] = str(self.options)
+        os.environ["PYKOTAFILENAME"] = str(self.inputfile)
+        #
+        # And opens the correct accounter 
         self.accounter = accounter.openAccounter(self)
     
@@ -683,4 +713,7 @@
             while destination.startswith("/") :
                 destination = destination[1:]
+            checkauth = destination.split("@", 1)    
+            if len(checkauth) == 2 :
+                destination = checkauth[1]
             printerhostname = destination.split("/")[0].split(":")[0]
             return ("CUPS", \
@@ -737,5 +770,5 @@
            was returned by the external command.
         """
-        for passnumber in range(1, 3) :
+        for dummy in range(1, 3) :
             printer = self.storage.getPrinter(self.printername)
             user = self.storage.getUser(self.username)
Index: pykota/trunk/pykota/reporters/html.py
===================================================================
--- pykota/trunk/pykota/reporters/html.py (revision 1239)
+++ pykota/trunk/pykota/reporters/html.py (revision 1240)
@@ -22,6 +22,6 @@
 #
 # $Log$
-# Revision 1.2  2003/12/27 15:43:36  uid67467
-# Savannah is back online...
+# Revision 1.3  2003/12/27 16:49:25  uid67467
+# Should be ok now.
 #
 # Revision 1.1  2003/12/02 14:41:17  jalet
@@ -56,9 +56,9 @@
                 for (group, grouppquota) in self.tool.storage.getPrinterGroupsAndQuotas(printer, self.ugnames) :
                     oddeven += 1
-                    if oddeven % 1 :
+                    if oddeven % 2 :
                         oddevenclass = "odd"
                     else :    
                         oddevenclass = "even"
-                    (pages, money, name, reached, soft, hard, balance, datelimit, lifepagecounter, lifetimepaid) = self.getQuota(group, grouppquota)
+                    (pages, money, name, reached, pagecounter, soft, hard, balance, datelimit, lifepagecounter, lifetimepaid) = self.getQuota(group, grouppquota)
                     self.report.append('<tr class="%s">%s</tr>' % (oddevenclass, "".join(["<td>%s</td>" % h for h in (name, reached, pagecounter, soft, hard, balance, datelimit or "&nbsp;", lifepagecounter, lifetimepaid)])))
                     total += pages
@@ -68,5 +68,5 @@
                 for (user, userpquota) in self.tool.storage.getPrinterUsersAndQuotas(printer, self.ugnames) :
                     oddeven += 1
-                    if oddeven % 1 :
+                    if oddeven % 2 :
                         oddevenclass = "odd"
                     else :    
Index: pykota/trunk/pykota/reporters/text.py
===================================================================
--- pykota/trunk/pykota/reporters/text.py (revision 1235)
+++ pykota/trunk/pykota/reporters/text.py (revision 1240)
@@ -22,4 +22,7 @@
 #
 # $Log$
+# Revision 1.7  2003/12/27 16:49:25  uid67467
+# Should be ok now.
+#
 # Revision 1.6  2003/12/02 14:40:21  jalet
 # Some code refactoring.
@@ -45,6 +48,4 @@
 #
 
-from mx import DateTime
-
 from pykota.reporter import BaseReporter, PyKotaReporterError
     
@@ -68,5 +69,5 @@
                 self.report.append('-' * len(header))
                 for (group, grouppquota) in self.tool.storage.getPrinterGroupsAndQuotas(printer, self.ugnames) :
-                    (pages, money, name, reached, soft, hard, balance, datelimit, lifepagecounter, lifetimepaid) = self.getQuota(group, grouppquota)
+                    (pages, money, name, reached, pagecounter, soft, hard, balance, datelimit, lifepagecounter, lifetimepaid) = self.getQuota(group, grouppquota)
                     self.report.append("%-9.9s %s %7i %7s %7s %10s %-10.10s %8i %10s" % (name, reached, pagecounter, soft, hard, balance, datelimit, lifepagecounter, lifetimepaid))
                     total += pages
Index: pykota/trunk/pykota/accounters/stupid.py
===================================================================
--- pykota/trunk/pykota/accounters/stupid.py (revision 1239)
+++ pykota/trunk/pykota/accounters/stupid.py (revision 1240)
@@ -22,6 +22,6 @@
 #
 # $Log$
-# Revision 1.9  2003/12/27 15:43:36  uid67467
-# Savannah is back online...
+# Revision 1.10  2003/12/27 16:49:25  uid67467
+# Should be ok now.
 #
 # Revision 1.8  2003/11/23 19:01:37  jalet
Index: pykota/trunk/pykota/accounters/external.py
===================================================================
--- pykota/trunk/pykota/accounters/external.py (revision 1239)
+++ pykota/trunk/pykota/accounters/external.py (revision 1240)
@@ -22,6 +22,6 @@
 #
 # $Log$
-# Revision 1.11  2003/12/27 15:43:36  uid67467
-# Savannah is back online...
+# Revision 1.12  2003/12/27 16:49:25  uid67467
+# Should be ok now.
 #
 # Revision 1.10  2003/11/23 19:01:36  jalet
Index: pykota/trunk/pykota/accounters/querying.py
===================================================================
--- pykota/trunk/pykota/accounters/querying.py (revision 1239)
+++ pykota/trunk/pykota/accounters/querying.py (revision 1240)
@@ -22,6 +22,6 @@
 #
 # $Log$
-# Revision 1.9  2003/12/27 15:43:36  uid67467
-# Savannah is back online...
+# Revision 1.10  2003/12/27 16:49:25  uid67467
+# Should be ok now.
 #
 # Revision 1.8  2003/11/21 14:28:46  jalet
@@ -70,5 +70,6 @@
         """Returns the printer's internal page counter."""
         global MAXTRIES, TIMETOSLEEP
-        for i in range(MAXTRIES) :
+        self.filter.logdebug("Reading printer's internal page counter...")
+        for dummy in range(MAXTRIES) :
             try :
                 counter = self.requester.getPrinterPageCounter(self.filter.printerhostname)
@@ -82,4 +83,5 @@
                 break
             time.sleep(TIMETOSLEEP)    
+        self.filter.logdebug("Printer's internal page counter value is : %s" % str(counter))
         return counter    
         
Index: pykota/trunk/pykota/logger.py
===================================================================
--- pykota/trunk/pykota/logger.py (revision 1218)
+++ pykota/trunk/pykota/logger.py (revision 1240)
@@ -22,4 +22,7 @@
 #
 # $Log$
+# Revision 1.11  2003/12/27 16:49:25  uid67467
+# Should be ok now.
+#
 # Revision 1.10  2003/11/25 22:37:22  jalet
 # Small code move
@@ -69,5 +72,5 @@
     __str__ = __repr__
 
-def openLogger(pykotatool, backend) :
+def openLogger(backend) :
     """Returns the appropriate logger subsystem object."""
     try :
@@ -76,3 +79,3 @@
         raise PyKotaLoggingError, _("Unsupported logging subsystem %s") % backend
     else :    
-        return getattr(loggingbackend, "Logger")()
+        return loggingbackend.Logger()
Index: pykota/trunk/pykota/requester.py
===================================================================
--- pykota/trunk/pykota/requester.py (revision 1219)
+++ pykota/trunk/pykota/requester.py (revision 1240)
@@ -22,4 +22,7 @@
 #
 # $Log$
+# Revision 1.11  2003/12/27 16:49:25  uid67467
+# Should be ok now.
+#
 # Revision 1.10  2003/11/25 23:46:40  jalet
 # Don't try to verify if module name is valid, Python does this better than us.
@@ -75,3 +78,3 @@
         raise PyKotaRequesterError, _("Unsupported requester backend %s") % backend
     else :    
-        return getattr(requesterbackend, "Requester")(printername, args)
+        return requesterbackend.Requester(printername, args)
Index: pykota/trunk/pykota/storages/pgstorage.py
===================================================================
--- pykota/trunk/pykota/storages/pgstorage.py (revision 1228)
+++ pykota/trunk/pykota/storages/pgstorage.py (revision 1240)
@@ -22,4 +22,7 @@
 #
 # $Log$
+# Revision 1.25  2003/12/27 16:49:25  uid67467
+# Should be ok now.
+#
 # Revision 1.24  2003/11/29 22:02:14  jalet
 # Don't try to retrieve the user print quota information if current printer
@@ -392,4 +395,15 @@
         return groupsandquotas
         
+    def getParentPrinters(self, printer) :    
+        """Get all the printer groups this printer is a member of."""
+        pgroups = []
+        result = self.doSearch("SELECT printername FROM printergroupsmembers JOIN printers ON groupid=id WHERE printerid=%s;" % self.doQuote(printer.ident))
+        if result :
+            for record in result :
+                parentprinter = self.getPrinter(record.get("printername"))
+                if parentprinter.Exists :
+                    pgroups.append(parentprinter)
+        return pgroups
+        
     def addPrinter(self, printername) :        
         """Adds a printer to the quota storage, returns it."""
@@ -468,5 +482,5 @@
                 self.doModify("INSERT INTO jobhistory (userid, printerid, jobid, pagecounter, action, jobsize, jobprice, filename, title, copies, options) VALUES (%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)))
             else :    
-                self.doModify("INSERT INTO jobhistory (userid, printerid, jobid, pagecounter, action, filename, title, copies, options) VALUES (%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.doModify("INSERT INTO jobhistory (userid, printerid, jobid, pagecounter, action, filename, title, copies, options) VALUES (%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)))
         else :        
             # here we explicitly want to reset jobsize to NULL if needed
Index: pykota/trunk/pykota/storages/ldapstorage.py
===================================================================
--- pykota/trunk/pykota/storages/ldapstorage.py (revision 1228)
+++ pykota/trunk/pykota/storages/ldapstorage.py (revision 1240)
@@ -22,4 +22,7 @@
 #
 # $Log$
+# Revision 1.41  2003/12/27 16:49:25  uid67467
+# Should be ok now.
+#
 # Revision 1.40  2003/11/29 22:02:14  jalet
 # Don't try to retrieve the user print quota information if current printer
@@ -166,4 +169,5 @@
 #
 
+import types
 import time
 import md5
@@ -211,4 +215,14 @@
         return md5.md5("%s" % time.time()).hexdigest()
         
+    def normalizeFields(self, fields) :    
+        """Ensure all items are lists."""
+        for (k, v) in fields.items() :
+            if type(v) not in (types.TupleType, types.ListType) :
+                if not v :
+                    del fields[k]
+                else :    
+                    fields[k] = [ v ]
+        return fields        
+        
     def beginTransaction(self) :    
         """Starts a transaction."""
@@ -237,4 +251,5 @@
     def doAdd(self, dn, fields) :
         """Adds an entry in the LDAP directory."""
+        fields = self.normalizeFields(fields)
         try :
             self.tool.logdebug("QUERY : ADD(%s, %s)" % (dn, str(fields)))
@@ -255,4 +270,5 @@
     def doModify(self, dn, fields, ignoreold=1) :
         """Modifies an entry in the LDAP directory."""
+        fields = self.normalizeFields(fields)
         try :
             oldentry = self.doSearch("objectClass=*", base=dn, scope=ldap.SCOPE_BASE)
@@ -545,4 +561,15 @@
         return groupsandquotas
         
+    def getParentPrinters(self, printer) :    
+        """Get all the printer groups this printer is a member of."""
+        pgroups = []
+        result = self.doSearch("(&(objectClass=pykotaPrinter)(uniqueMember=%s))" % printer.ident, ["pykotaPrinterName"], base=self.info["printerbase"])
+        if result :
+            for (printerid, fields) in result :
+                parentprinter = self.getPrinter(fields.get("pykotaPrinterName")[0])
+                if parentprinter.Exists :
+                    pgroups.append(parentprinter)
+        return pgroups
+        
     def addPrinter(self, printername) :        
         """Adds a printer to the quota storage, returns it."""
@@ -679,5 +706,5 @@
         """Sets the date limit permanently for a user print quota."""
         fields = {
-                   "pykotaDateLimit" : "%04i-%02i-%02i %02i:%02i:%02i" % (datelimit.year, datelimit.month, datelimit.day, datelimit.hour, datelimit.minute, datelimit.second),
+                   "pykotaDateLimit" : datelimit,
                  }
         return self.doModify(userpquota.ident, fields)
@@ -686,5 +713,5 @@
         """Sets the date limit permanently for a group print quota."""
         fields = {
-                   "pykotaDateLimit" : "%04i-%02i-%02i %02i:%02i:%02i" % (datelimit.year, datelimit.month, datelimit.day, datelimit.hour, datelimit.minute, datelimit.second),
+                   "pykotaDateLimit" : datelimit,
                  }
         return self.doModify(grouppquota.ident, fields)
Index: pykota/trunk/pykota/reporter.py
===================================================================
--- pykota/trunk/pykota/reporter.py (revision 1239)
+++ pykota/trunk/pykota/reporter.py (revision 1240)
@@ -22,6 +22,6 @@
 #
 # $Log$
-# Revision 1.5  2003/12/27 15:43:36  uid67467
-# Savannah is back online...
+# Revision 1.6  2003/12/27 16:49:25  uid67467
+# Should be ok now.
 #
 # Revision 1.4  2003/12/02 14:40:21  jalet
@@ -41,4 +41,6 @@
 #
 #
+
+from mx import DateTime
 
 class PyKotaReporterError(Exception):
@@ -96,17 +98,20 @@
                 datelimit = "DENY"
                 reached = "+B"
+            elif balance <= self.tool.config.getPoorMan() :
+                datelimit = "WARNING"
+                reached = "?B"
             else :    
                 datelimit = ""
                 reached = "-B"
         else :
-            if quota.DateLimit is not None :
+            if (quota.HardLimit is not None) and (pagecounter >= quota.HardLimit) :    
+                datelimit = "DENY"
+            elif (quota.HardLimit is None) and (quota.SoftLimit is not None) and (pagecounter >= quota.SoftLimit) :
+                datelimit = "DENY"
+            elif quota.DateLimit is not None :
                 now = DateTime.now()
                 datelimit = DateTime.ISO.ParseDateTime(quota.DateLimit)
                 if now >= datelimit :
                     datelimit = "DENY"
-            elif (quota.HardLimit is not None) and (pagecounter >= quota.HardLimit) :    
-                datelimit = "DENY"
-            elif (quota.HardLimit is None) and (quota.SoftLimit is not None) and (pagecounter >= quota.SoftLimit) :
-                datelimit = "DENY"
             else :    
                 datelimit = ""
@@ -124,3 +129,3 @@
         raise PyKotaReporterError, _("Unsupported reporter backend %s") % reporttype
     else :    
-        return getattr(reporterbackend, "Reporter")(tool, printers, ugnames, isgroup)
+        return reporterbackend.Reporter(tool, printers, ugnames, isgroup)
Index: pykota/trunk/pykota/accounter.py
===================================================================
--- pykota/trunk/pykota/accounter.py (revision 1239)
+++ pykota/trunk/pykota/accounter.py (revision 1240)
@@ -22,6 +22,6 @@
 #
 # $Log$
-# Revision 1.8  2003/12/27 15:43:36  uid67467
-# Savannah is back online...
+# Revision 1.9  2003/12/27 16:49:25  uid67467
+# Should be ok now.
 #
 # Revision 1.7  2003/11/25 23:46:40  jalet
@@ -144,4 +144,9 @@
         return action
         
+    def computeJobSize(self) :    
+        """Must be overriden in children classes."""
+        raise RuntimeError, "AccounterBase.computeJobSize() must be overriden !"
+        
+        
 def openAccounter(kotafilter) :
     """Returns a connection handle to the appropriate accounter."""
@@ -152,3 +157,3 @@
         raise PyKotaAccounterError, _("Unsupported accounter backend %s") % backend
     else :    
-        return getattr(accounterbackend, "Accounter")(kotafilter, args)
+        return accounterbackend.Accounter(kotafilter, args)
Index: pykota/trunk/pykota/storage.py
===================================================================
--- pykota/trunk/pykota/storage.py (revision 1219)
+++ pykota/trunk/pykota/storage.py (revision 1240)
@@ -22,4 +22,7 @@
 #
 # $Log$
+# Revision 1.29  2003/12/27 16:49:25  uid67467
+# Should be ok now.
+#
 # Revision 1.28  2003/11/25 23:46:40  jalet
 # Don't try to verify if module name is valid, Python does this better than us.
@@ -248,4 +251,5 @@
         self.HardLimit = None
         self.DateLimit = None
+        self.ParentPrintersUserPQuota = (user.Exists and printer.Exists and parent.getParentPrintersUserPQuota(self)) or []
         
     def setDateLimit(self, datelimit) :    
@@ -268,20 +272,20 @@
     def increasePagesUsage(self, nbpages) :
         """Increase the value of used pages and money."""
-        if nbpages :
-            jobprice = (float(self.Printer.PricePerPage or 0.0) * nbpages) + float(self.Printer.PricePerJob or 0.0)
-            newpagecounter = int(self.PageCounter or 0) + nbpages
-            newlifepagecounter = int(self.LifePageCounter or 0) + nbpages
-            self.parent.beginTransaction()
-            try :
-                if jobprice : # optimization : don't access the database if unneeded.
-                    self.User.consumeAccountBalance(jobprice)
-                self.parent.writeUserPQuotaPagesCounters(self, newpagecounter, newlifepagecounter)
-            except PyKotaStorageError, msg :    
-                self.parent.rollbackTransaction()
-                raise PyKotaStorageError, msg
-            else :    
-                self.parent.commitTransaction()
-                self.PageCounter = newpagecounter
-                self.LifePageCounter = newlifepagecounter
+        jobprice = (float(self.Printer.PricePerPage or 0.0) * nbpages) + float(self.Printer.PricePerJob or 0.0)
+        self.parent.beginTransaction()
+        try :
+            if nbpages :
+                self.User.consumeAccountBalance(jobprice)
+                for upq in [ self ] + self.ParentPrintersUserPQuota :
+                    newpagecounter = int(upq.PageCounter or 0) + nbpages
+                    newlifepagecounter = int(upq.LifePageCounter or 0) + nbpages
+                    self.parent.writeUserPQuotaPagesCounters(upq, newpagecounter, newlifepagecounter)
+                    upq.PageCounter = newpagecounter
+                    upq.LifePageCounter = newlifepagecounter
+        except PyKotaStorageError, msg :    
+            self.parent.rollbackTransaction()
+            raise PyKotaStorageError, msg
+        else :    
+            self.parent.commitTransaction()
         
 class StorageGroupPQuota(StorageObject) :
@@ -344,4 +348,8 @@
             self.tool.logdebug("Caching enabled.")
             self.caches = { "USERS" : {}, "GROUPS" : {}, "PRINTERS" : {}, "USERPQUOTAS" : {}, "GROUPPQUOTAS" : {}, "JOBS" : {}, "LASTJOBS" : {} }
+        
+    def close(self) :    
+        """Must be overriden in children classes."""
+        raise RuntimeError, "BaseStorage.close() must be overriden !"
         
     def __del__(self) :        
@@ -441,4 +449,11 @@
         return user.Groups   
         
+    def getParentPrintersUserPQuota(self, userpquota) :     
+        """Returns all user print quota on the printer and its parents."""
+        upquotas = [ ]
+        for printer in self.getParentPrinters(userpquota.Printer) :
+            upquotas.append(self.getUserPQuota(userpquota.User, printer))
+        return upquotas        
+        
 def openConnection(pykotatool) :
     """Returns a connection handle to the appropriate Quota Storage Database."""
@@ -454,4 +469,4 @@
         admin = backendinfo["storageadmin"] or backendinfo["storageuser"]
         adminpw = backendinfo["storageadminpw"] or backendinfo["storageuserpw"]
-        return getattr(storagebackend, "Storage")(pykotatool, host, database, admin, adminpw)
+        return storagebackend.Storage(pykotatool, host, database, admin, adminpw)
 
Index: pykota/trunk/pykota/version.py
===================================================================
--- pykota/trunk/pykota/version.py (revision 1239)
+++ pykota/trunk/pykota/version.py (revision 1240)
@@ -22,5 +22,5 @@
 #
 
-__version__ = "1.16alpha17_unofficial"
+__version__ = "1.16alpha20_unofficial"
 
 __doc__ = """PyKota : a complete Printing Quota Solution for CUPS and LPRng."""
