Index: pykota/trunk/pykota/storages/sql.py
===================================================================
--- pykota/trunk/pykota/storages/sql.py (revision 2678)
+++ pykota/trunk/pykota/storages/sql.py (revision 2686)
@@ -547,20 +547,13 @@
         return self.getGroupPQuota(group, printer)
         
-    def writePrinterPrices(self, printer) :    
-        """Write the printer's prices back into the storage."""
-        self.doModify("UPDATE printers SET priceperpage=%s, priceperjob=%s WHERE id=%s" % (self.doQuote(printer.PricePerPage), self.doQuote(printer.PricePerJob), self.doQuote(printer.ident)))
-        
-    def writePrinterDescription(self, printer) :    
-        """Write the printer's description back into the storage."""
-        description = self.userCharsetToDatabase(printer.Description)
-        self.doModify("UPDATE printers SET description=%s WHERE id=%s" % (self.doQuote(description), self.doQuote(printer.ident)))
-        
-    def setPrinterMaxJobSize(self, printer, maxjobsize) :     
-        """Write the printer's maxjobsize attribute."""
-        self.doModify("UPDATE printers SET maxjobsize=%s WHERE id=%s" % (self.doQuote(maxjobsize), self.doQuote(printer.ident)))
-        
-    def setPrinterPassThroughMode(self, printer, passthrough) :
-        """Write the printer's passthrough attribute."""
-        self.doModify("UPDATE printers SET passthrough=%s WHERE id=%s" % (self.doQuote((passthrough and "t") or "f"), self.doQuote(printer.ident)))
+    def savePrinter(self, printer) :    
+        """Saves the printer to the database in a single operation."""
+        self.doModify("UPDATE printers SET passthrough=%s, maxjobsize=%s, description=%s, priceperpage=%s, priceperjob=%s WHERE id=%s" \
+                              % (self.doQuote((printer.PassThrough and "t") or "f"), \
+                                 self.doQuote(printer.MaxJobSize), \
+                                 self.doQuote(self.userCharsetToDatabase(printer.Description)), \
+                                 self.doQuote(printer.PricePerPage), \
+                                 self.doQuote(printer.PricePerJob), \
+                                 self.doQuote(printer.ident)))
         
     def writeUserOverCharge(self, user, factor) :
Index: pykota/trunk/pykota/storages/ldapstorage.py
===================================================================
--- pykota/trunk/pykota/storages/ldapstorage.py (revision 2680)
+++ pykota/trunk/pykota/storages/ldapstorage.py (revision 2686)
@@ -1017,31 +1017,12 @@
         return self.getGroupPQuota(group, printer)
         
-    def writePrinterPrices(self, printer) :    
-        """Write the printer's prices back into the storage."""
+    def savePrinter(self, printer) :    
+        """Saves the printer to the database in a single operation."""
         fields = {
+                   "pykotaPassThrough" : (printer.PassThrough and "t") or "f",
+                   "pykotaMaxJobSize" : (printer.MaxJobSize and str(printer.MaxJobSize)) or "0",
+                   "description" : self.userCharsetToDatabase(printer.Description or ""),
                    "pykotaPricePerPage" : str(printer.PricePerPage),
                    "pykotaPricePerJob" : str(printer.PricePerJob),
-                 }
-        self.doModify(printer.ident, fields)
-        
-    def writePrinterDescription(self, printer) :    
-        """Write the printer's description back into the storage."""
-        fields = {
-                   "description" : self.userCharsetToDatabase(printer.Description or ""),
-                 }
-        if fields["description"] :
-            self.doModify(printer.ident, fields)
-            
-    def setPrinterMaxJobSize(self, printer, maxjobsize) :     
-        """Write the printer's maxjobsize attribute."""
-        fields = {
-                   "pykotaMaxJobSize" : (maxjobsize and str(maxjobsize)) or "0",
-                 }
-        self.doModify(printer.ident, fields)
-        
-    def setPrinterPassThroughMode(self, printer, passthrough) :
-        """Write the printer's passthrough attribute."""
-        fields = {
-                   "pykotaPassThrough" : (passthrough and "t") or "f",
                  }
         self.doModify(printer.ident, fields)
Index: pykota/trunk/pykota/storage.py
===================================================================
--- pykota/trunk/pykota/storage.py (revision 2676)
+++ pykota/trunk/pykota/storage.py (revision 2686)
@@ -164,4 +164,10 @@
             raise AttributeError, name
             
+    def save(self) :    
+        """Saves the billing code to disk in a single operation."""
+        if self.isDirty :
+            self.parent.savePrinter(self)
+            self.isDirty = False
+            
     def addJobToHistory(self, jobid, user, pagecounter, action, jobsize=None, jobprice=None, filename=None, title=None, copies=None, options=None, clienthost=None, jobsizebytes=None, jobmd5sum=None, jobpages=None, jobbilling=None, precomputedsize=None, precomputedprice=None) :
         """Adds a job to the printer's history."""
@@ -190,5 +196,5 @@
         else :    
             self.PricePerJob = float(priceperjob)
-        self.parent.writePrinterPrices(self)
+        self.isDirty = True    
         
     def setDescription(self, description=None) :
@@ -198,5 +204,15 @@
         else :    
             self.Description = str(description)
-        self.parent.writePrinterDescription(self)
+        self.isDirty = True    
+        
+    def setPassThrough(self, passthrough) :
+        """Sets the printer's passthrough mode."""
+        self.PassThrough = passthrough
+        self.isDirty = True
+        
+    def setMaxJobSize(self, maxjobsize) :
+        """Sets the printer's maximal job size."""
+        self.MaxJobSize = maxjobsize
+        self.isDirty = True
         
     def delete(self) :    
@@ -218,5 +234,6 @@
                     if v.Printer.Name == self.Name :
                         self.parent.flushEntry("GROUPPQUOTAS", "%s@%s" % (v.Group.Name, v.Printer.Name))
-            self.Exists = 0    
+            self.isDirty = False            
+            self.Exists = False
         
 class StorageUserPQuota(StorageObject) :
@@ -453,15 +470,12 @@
         self.parent.deleteBillingCode(self)
         self.parent.flushEntry("BILLINGCODES", self.BillingCode)
+        self.isDirty = False
         self.Exists = False
-        self.isDirty = False
         
     def reset(self, balance=0.0, pagecounter=0) :    
         """Resets the pagecounter and balance for this billing code."""
-        if self.Balance != balance :
-            self.Balance = balance
-            self.isDirty = True
-        if self.PageCounter != pagecounter :
-            self.PageCounter = pagecounter
-            self.isDirty = True
+        self.Balance = balance
+        self.PageCounter = pagecounter
+        self.isDirty = True
         
     def setDescription(self, description=None) :
Index: pykota/trunk/bin/edpykota
===================================================================
--- pykota/trunk/bin/edpykota (revision 2658)
+++ pykota/trunk/bin/edpykota (revision 2686)
@@ -392,4 +392,5 @@
                     (perpage, perjob) = charges
                     printer.setPrices(perpage, perjob)    
+                    printer.save()
                     
                 if options["prototype"] :
Index: pykota/trunk/bin/pkprinters
===================================================================
--- pykota/trunk/bin/pkprinters (revision 2674)
+++ pykota/trunk/bin/pkprinters (revision 2686)
@@ -170,38 +170,6 @@
                     raise PyKotaCommandLineError, _("There's no printer matching %s") % " ".join(names)
                         
-            if not options["list"] :
-                self.display("%s...\n" % _("Modification"))
-                
-            if options["groups"] :        
-                printersgroups = self.storage.getMatchingPrinters(options["groups"])
-                if not printersgroups :
-                    raise PyKotaCommandLineError, _("There's no printer matching %s") % " ".join(options["groups"].split(','))
-                    
-            if options["charge"] :
-                try :
-                    charges = [float(part) for part in options["charge"].split(',', 1)]
-                except ValueError :    
-                    raise PyKotaCommandLineError, _("Invalid charge amount value %s") % options["charge"]
-                else :    
-                    if len(charges) > 2 :
-                        charges = charges[:2]
-                    if len(charges) != 2 :
-                        charges = [charges[0], None]
-                    (perpage, perjob) = charges
-                    
-            if options["maxjobsize"] :        
-                try :
-                    maxjobsize = int(options["maxjobsize"])
-                    if maxjobsize < 0 :
-                        raise ValueError
-                except ValueError :    
-                    raise PyKotaCommandLineError, _("Invalid maximum job size value %s") % options["maxjobsize"]
-            else :        
-                maxjobsize = None
-                    
-            nbtotal = len(printers)
-            for i in range(nbtotal) :        
-                printer = printers[i]
-                if options["list"] :    
+            if options["list"] :
+                for printer in printers :
                     parents = ", ".join([p.Name for p in self.storage.getParentPrinters(printer)])
                     if parents : 
@@ -214,21 +182,59 @@
                     if parents :       
                         print "    %s" % parents
-                else :    
+            else :
+                self.display("%s...\n" % _("Modification"))
+                
+                if options["groups"] :        
+                    printersgroups = self.storage.getMatchingPrinters(options["groups"])
+                    if not printersgroups :
+                        raise PyKotaCommandLineError, _("There's no printer matching %s") % " ".join(options["groups"].split(','))
+                else :         
+                    printersgroups = []
+                        
+                if options["charge"] :
+                    try :
+                        charges = [float(part) for part in options["charge"].split(',', 1)]
+                    except ValueError :    
+                        raise PyKotaCommandLineError, _("Invalid charge amount value %s") % options["charge"]
+                    else :    
+                        if len(charges) > 2 :
+                            charges = charges[:2]
+                        if len(charges) != 2 :
+                            charges = [charges[0], None]
+                        (perpage, perjob) = charges
+                        
+                if options["maxjobsize"] :        
+                    try :
+                        maxjobsize = int(options["maxjobsize"])
+                        if maxjobsize < 0 :
+                            raise ValueError
+                    except ValueError :    
+                        raise PyKotaCommandLineError, _("Invalid maximum job size value %s") % options["maxjobsize"]
+                else :        
+                    maxjobsize = None
+                        
+                if options["description"] :
+                    description = options["description"].strip()
+                nopassthrough = options["nopassthrough"]    
+                passthrough = options["passthrough"]
+                nbtotal = len(printers)
+                for i in range(nbtotal) :        
+                    printer = printers[i]
                     if options["charge"] :
                         printer.setPrices(perpage, perjob)    
-                    if options["description"] is not None :
-                        printer.setDescription(options["description"].strip())
-                    if options["nopassthrough"] and printer.PassThrough :    
-                        self.storage.setPrinterPassThroughMode(printer, 0)
-                    if options["passthrough"] and not printer.PassThrough :    
-                        self.storage.setPrinterPassThroughMode(printer, 1)
+                    if description is not None :        # NB : "" is allowed !
+                        printer.setDescription(description)
+                    if nopassthrough and printer.PassThrough :    
+                        printer.setPassThrough(False)
+                    if passthrough and not printer.PassThrough :    
+                        printer.setPassThrough(True)
                     if (maxjobsize is not None) and (printer.MaxJobSize != maxjobsize) :    
-                        self.storage.setPrinterMaxJobSize(printer, maxjobsize)
-                    if options["groups"] :    
-                        for pgroup in printersgroups :
-                            if options["remove"] :
-                                pgroup.delPrinterFromGroup(printer)
-                            else :
-                                pgroup.addPrinterToGroup(printer)    
+                        printer.setMaxJobSize(maxjobsize)
+                    printer.save()    
+                    for pgroup in printersgroups :
+                        if options["remove"] :
+                            pgroup.delPrinterFromGroup(printer)
+                        else :
+                            pgroup.addPrinterToGroup(printer)    
                     percent = 100.0 * float(i) / float(nbtotal)
                     self.display("\r%.02f%%" % percent)
Index: pykota/trunk/NEWS
===================================================================
--- pykota/trunk/NEWS (revision 2677)
+++ pykota/trunk/NEWS (revision 2686)
@@ -24,5 +24,6 @@
     - 1.24alpha11 :
     
-        - Huge speed improvements when modifying billing codes.
+        - Huge speed improvements when modifying billing codes and
+          printers.
         
         - Both pkbcodes and pkprinters now display the percent done
