root / pykota / trunk / pykota / storages / ldapstorage.py @ 1030

Revision 1030, 24.8 kB (checked in by jalet, 21 years ago)

More work on LDAP

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1# PyKota
2#
3# PyKota : Print Quotas for CUPS and LPRng
4#
5# (c) 2003 Jerome Alet <alet@librelogiciel.com>
6# This program is free software; you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation; either version 2 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program; if not, write to the Free Software
18# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
19#
20# $Id$
21#
22# $Log$
23# Revision 1.8  2003/06/15 22:26:52  jalet
24# More work on LDAP
25#
26# Revision 1.7  2003/06/14 22:44:21  jalet
27# More work on LDAP storage backend.
28#
29# Revision 1.6  2003/06/13 19:07:57  jalet
30# Two big bugs fixed, time to release something ;-)
31#
32# Revision 1.5  2003/06/10 16:37:54  jalet
33# Deletion of the second user which is not needed anymore.
34# Added a debug configuration field in /etc/pykota.conf
35# All queries can now be sent to the logger in debug mode, this will
36# greatly help improve performance when time for this will come.
37#
38# Revision 1.4  2003/06/10 10:45:32  jalet
39# Not implemented methods now raise an exception when called.
40#
41# Revision 1.3  2003/06/06 20:49:15  jalet
42# Very latest schema. UNTESTED.
43#
44# Revision 1.2  2003/06/06 14:21:08  jalet
45# New LDAP schema.
46# Small bug fixes.
47#
48# Revision 1.1  2003/06/05 11:19:13  jalet
49# More good work on LDAP storage.
50#
51#
52#
53
54#
55# My IANA assigned number, for
56# "Conseil Internet & Logiciels Libres, J�me Alet"
57# is 16868. Use this as a base to create the LDAP schema.
58#
59
60import time
61import md5
62import fnmatch
63
64from pykota.storage import PyKotaStorageError
65
66try :
67    import ldap
68    from ldap import modlist
69except ImportError :   
70    import sys
71    # TODO : to translate or not to translate ?
72    raise PyKotaStorageError, "This python version (%s) doesn't seem to have the python-ldap module installed correctly." % sys.version.split()[0]
73   
74class Storage :
75    def __init__(self, pykotatool, host, dbname, user, passwd) :
76        """Opens the LDAP connection."""
77        # raise PyKotaStorageError, "Sorry, the LDAP backend for PyKota is not yet implemented !"
78        self.closed = 1
79        self.tool = pykotatool
80        self.debug = pykotatool.config.getDebug()
81        self.info = pykotatool.config.getLDAPInfo()
82        try :
83            self.database = ldap.initialize(host) 
84            self.database.simple_bind_s(user, passwd)
85            self.basedn = dbname
86        except ldap.SERVER_DOWN :   
87            raise PyKotaStorageError, "LDAP backend for PyKota seems to be down !" # TODO : translate
88        else :   
89            self.closed = 0
90            if self.debug :
91                self.tool.logger.log_message("Database opened (host=%s, dbname=%s, user=%s)" % (host, dbname, user), "debug")
92           
93    def __del__(self) :       
94        """Closes the database connection."""
95        if not self.closed :
96            del self.database
97            self.closed = 1
98            if self.debug :
99                self.tool.logger.log_message("Database closed.", "debug")
100       
101    def genUUID(self) :   
102        """Generates an unique identifier.
103       
104           TODO : this one is not unique accross several print servers, but should be sufficient for testing.
105        """
106        return md5.md5("%s" % time.time()).hexdigest()
107       
108    def doSearch(self, key, fields=None, base="", scope=ldap.SCOPE_SUBTREE) :
109        """Does an LDAP search query."""
110        try :
111            base = base or self.basedn
112            if self.debug :
113                self.tool.logger.log_message("QUERY : BaseDN : %s, Scope : %s, Filter : %s, Attributes : %s" % (base, scope, key, fields), "debug")
114            result = self.database.search_s(base or self.basedn, scope, key, fields)
115        except ldap.LDAPError :   
116            raise PyKotaStorageError, _("Search for %s(%s) from %s(scope=%s) returned no answer.") % (key, fields, base, scope)
117        else :     
118            return result
119           
120    def doAdd(self, dn, fields) :
121        """Adds an entry in the LDAP directory."""
122        try :
123            if self.debug :
124                self.tool.logger.log_message("QUERY : ADD(%s, %s)" % (dn, str(fields)), "debug")
125            self.database.add_s(dn, modlist.addModlist(fields))
126        except ldap.LDAPError :
127            raise PyKotaStorageError, _("Problem adding LDAP entry (%s, %s)") % (dn, str(fields))
128        else :
129            return dn
130           
131    def doModify(self, dn, fields) :
132        """Modifies an entry in the LDAP directory."""
133        try :
134            oldentry = self.doSearch("objectClass=*", base=dn, scope=ldap.SCOPE_BASE)
135            if self.debug :
136                self.tool.logger.log_message("QUERY : Modify(%s, %s ==> %s)" % (dn, oldentry[0][1], fields), "debug")
137            self.database.modify_s(dn, modlist.modifyModlist(oldentry[0][1], fields, ignore_oldexistent=1))
138        except ldap.LDAPError :
139            raise PyKotaStorageError, _("Problem modifying LDAP entry (%s, %s)") % (dn, fields)
140        else :
141            return dn
142       
143    def getMatchingPrinters(self, printerpattern) :
144        """Returns the list of all printers as tuples (id, name) for printer names which match a certain pattern."""
145        result = self.doSearch("objectClass=pykotaPrinter", ["pykotaPrinterName"], base=self.info["printerbase"])
146        if result :
147            return [(printerid, printer["pykotaPrinterName"][0]) for (printerid, printer) in result if fnmatch.fnmatchcase(printer["pykotaPrinterName"][0], printerpattern)]
148           
149    def getPrinterId(self, printername) :       
150        """Returns a printerid given a printername."""
151        result = self.doSearch("(&(objectClass=pykotaPrinter)(|(pykotaPrinterName=%s)(%s=%s)))" % (printername, self.info["printerrdn"], printername), ["pykotaPrinterName"], base=self.info["printerbase"])
152        if result :
153            return result[0][0]
154           
155    def getPrinterName(self, printerid) :       
156        """Returns a printerid given a printer id."""
157        result = self.doSearch("objectClass=*", ["pykotaPrinterName"], base=printerid, scope=ldap.SCOPE_BASE)
158        if result :
159            return result[0][1]["pykotaPrinterName"][0]
160           
161    def getPrinterPrices(self, printerid) :       
162        """Returns a printer prices per page and per job given a printerid."""
163        result = self.doSearch("(|(pykotaPrinterName=*)(%s=*))" % self.info["printerrdn"], ["pykotaPricePerPage", "pykotaPricePerJob"], base=printerid, scope=ldap.SCOPE_BASE)
164        if result :
165            return (float(result[0][1]["pykotaPricePerPage"][0]), float(result[0][1]["pykotaPricePerJob"][0]))
166           
167    def setPrinterPrices(self, printerid, perpage, perjob) :
168        """Sets prices per job and per page for a given printer."""
169        raise PyKotaStorageError, "Not implemented !"
170   
171    def getUserId(self, username) :
172        """Returns a userid given a username."""
173        result = self.doSearch("(&(objectClass=pykotaAccount)(|(pykotaUserName=%s)(%s=%s)))" % (username, self.info["userrdn"], username), [self.info["userrdn"]], base=self.info["userbase"])
174        if result :
175            return result[0][0]
176           
177    def getGroupId(self, groupname) :
178        """Returns a groupid given a grupname."""
179        result = self.doSearch("(&(objectClass=pykotaGroup)(|(pykotaGroupName=%s)(%s=%s)))" % (groupname, self.info["grouprdn"], groupname), [self.info["grouprdn"]], base=self.info["groupbase"])
180        if result is not None :
181            (groupid, dummy) = result[0]
182            return groupid
183           
184    def getJobHistoryId(self, jobid, userid, printerid) :       
185        """Returns the history line's id given a (jobid, userid, printerid).
186       
187           TODO : delete because shouldn't be needed by the LDAP backend
188        """
189        raise PyKotaStorageError, "Not implemented !"
190           
191    def getPrinterUsers(self, printerid) :       
192        """Returns the list of userids and usernames which uses a given printer."""
193        # first get the printer's name from the id
194        result = self.doSearch("objectClass=pykotaPrinter", ["pykotaPrinterName", self.info["printerrdn"]], base=printerid, scope=ldap.SCOPE_BASE)
195        if result :
196            fields = result[0][1]
197            printername = (fields.get("pykotaPrinterName") or fields.get(self.info["printerrdn"]))[0]
198            result = self.doSearch("(&(objectClass=pykotaUserPQuota)(pykotaPrinterName=%s))" % printername, ["pykotaUserName"], base=self.info["userquotabase"]) 
199            if result :
200                return [(pquotauserid, fields["pykotaUserName"][0]) for (pquotauserid, fields) in result]
201       
202    def getPrinterGroups(self, printerid) :       
203        """Returns the list of groups which uses a given printer."""
204        # first get the printer's name from the id
205        result = self.doSearch("objectClass=pykotaPrinter", ["pykotaPrinterName", self.info["printerrdn"]], base=printerid, scope=ldap.SCOPE_BASE)
206        if result :
207            fields = result[0][1]
208            printername = (fields.get("pykotaPrinterName") or fields.get(self.info["printerrdn"]))[0]
209            result = self.doSearch("(&(objectClass=pykotaGroupPQuota)(pykotaPrinterName=%s))" % printername, ["pykotaGroupName"], base=self.info["groupquotabase"]) 
210            if result :
211                return [(pquotagroupid, fields["pykotaGroupName"][0]) for (pquotagroupid, fields) in result]
212       
213    def getGroupMembersNames(self, groupname) :       
214        """Returns the list of user's names which are member of this group."""
215        result = self.doSearch("(&(objectClass=pykotaGroup)(|(pykotaGroupName=%s)(%s=%s)))" % (groupname, self.info["grouprdn"], groupname), [self.info["groupmembers"]])
216        if result :
217            fields = result[0][1]
218            return fields.get(self.info["groupmembers"])
219       
220    def getUserGroupsNames(self, userid) :       
221        """Returns the list of groups' names the user is a member of."""
222        raise PyKotaStorageError, "Not implemented !"
223       
224    def addPrinter(self, printername) :       
225        """Adds a printer to the quota storage, returns its id."""
226        fields = { self.info["printerrdn"] : printername,
227                   "objectClass" : ["pykotaObject", "pykotaPrinter"],
228                   "pykotaPrinterName" : printername,
229                   "pykotaPricePerPage" : "0.0",
230                   "pykotaPricePerJob" : "0.0",
231                 } 
232        dn = "%s=%s,%s" % (self.info["printerrdn"], printername, self.info["printerbase"])
233        return self.doAdd(dn, fields)
234       
235    def addUser(self, username) :       
236        """Adds a user to the quota storage, returns its id."""
237        fields = { self.info["userrdn"] : username,
238                   "objectClass" : ["pykotaObject", "pykotaAccount", "pykotaAccountBalance"],
239                   "pykotaUserName" : username,
240                   "pykotaLimitBy" : "quota",
241                   "pykotaBalance" : "0.0",
242                   "pykotaLifeTimePaid" : "0.0",
243                 } 
244        dn = "%s=%s,%s" % (self.info["userrdn"], username, self.info["userbase"])
245        return self.doAdd(dn, fields)
246       
247    def addGroup(self, groupname) :       
248        """Adds a group to the quota storage, returns its id."""
249        fields = { self.info["grouprdn"] : groupname,
250                   "objectClass" : ["pykotaObject", "pykotaGroup"],
251                   "pykotaGroupName" : groupname,
252                   "pykotaLimitBy" : "quota",
253                 } 
254        dn = "%s=%s,%s" % (self.info["grouprdn"], groupname, self.info["groupbase"])
255        return self.doAdd(dn, fields)
256       
257    def addUserPQuota(self, username, printerid) :
258        """Initializes a user print quota on a printer, adds the user to the quota storage if needed."""
259        uuid = self.genUUID()
260        fields = { "objectClass" : ["pykotaObject", "pykotaUserPQuota"],
261                   "cn" : uuid,
262                   "pykotaUserName" : username,
263                   "pykotaPrinterName" : self.getPrinterName(printerid), 
264                   "pykotaPageCounter" : "0",
265                   "pykotaLifePageCounter" : "0",
266                   "pykotaSoftLimit" : "0",
267                   "pykotaHardLimit" : "0",
268                   "pykotaDateLimit" : "None",
269                 } 
270        dn = "cn=%s,%s" % (uuid, self.info["userquotabase"])
271        return self.doAdd(dn, fields)
272       
273    def addGroupPQuota(self, groupname, printerid) :
274        """Initializes a group print quota on a printer, adds the group to the quota storage if needed."""
275        uuid = self.genUUID()
276        fields = { "objectClass" : ["pykotaObject", "pykotaGroupPQuota"],
277                   "cn" : uuid,
278                   "pykotaGroupName" : groupname,
279                   "pykotaPrinterName" : self.getPrinterName(printerid), 
280                   "pykotaSoftLimit" : "0",
281                   "pykotaHardLimit" : "0",
282                   "pykotaDateLimit" : "None",
283                 } 
284        dn = "cn=%s,%s" % (uuid, self.info["groupquotabase"])
285        return self.doAdd(dn, fields)
286       
287    def increaseUserBalance(self, userid, amount) :   
288        """Increases (or decreases) an user's account balance by a given amount."""
289        raise PyKotaStorageError, "Not implemented !"
290       
291    def getUserBalance(self, userquotaid) :   
292        """Returns the current account balance for a given user quota identifier."""
293        # first get the user's name from the user quota id
294        result = self.doSearch("objectClass=pykotaUserPQuota", ["pykotaUserName"], base=userquotaid, scope=ldap.SCOPE_BASE)
295        if result :
296            username = result[0][1]["pykotaUserName"][0]
297            result = self.doSearch("(&(objectClass=pykotaAccountBalance)(|(pykotaUserName=%s)(%s=%s)))" % (username, self.info["userrdn"], username), ["pykotaBalance", "pykotaLifeTimePaid"])
298            if result :
299                fields = result[0][1]
300                return (float(fields["pykotaBalance"][0]), float(fields["pykotaLifeTimePaid"][0]))
301       
302    def getUserBalanceFromUserId(self, userid) :   
303        """Returns the current account balance for a given user id."""
304        # first get the user's name from the user id
305        result = self.doSearch("objectClass=pykotaAccount", ["pykotaUserName", self.info["userrdn"]], base=userid, scope=ldap.SCOPE_BASE)
306        if result :
307            fields = result[0][1]
308            username = (fields.get("pykotaUserName") or fields.get(self.info["userrdn"]))[0]
309            result = self.doSearch("(&(objectClass=pykotaAccountBalance)(|(pykotaUserName=%s)(%s=%s)))" % (username, self.info["userrdn"], username), ["pykotaBalance", "pykotaLifeTimePaid"])
310            if result :
311                fields = result[0][1]
312                return (float(fields["pykotaBalance"][0]), float(fields["pykotaLifeTimePaid"][0]))
313       
314    def getGroupBalance(self, groupquotaid) :   
315        """Returns the current account balance for a given group, as the sum of each of its users' account balance."""
316        # first get the group's name from the group quota id
317        result = self.doSearch("objectClass=pykotaGroupPQuota", ["pykotaGroupName"], base=groupquotaid, scope=ldap.SCOPE_BASE)
318        if result :
319            groupname = result[0][1]["pykotaGroupName"][0]
320            members = self.getGroupMembersNames(groupname)
321            balance = lifetimepaid = 0.0
322            for member in members :
323                userid = self.getUserId(member)
324                if userid :
325                    userbal = self.getUserBalanceFromUserId(userid)
326                    if userbal :
327                        (bal, paid) = userbal
328                        balance += bal
329                        lifetimepaid += paid
330            return (balance, lifetimepaid)           
331       
332    def getUserLimitBy(self, userquotaid) :   
333        """Returns the way in which user printing is limited."""
334        result = self.doSearch("objectClass=pykotaUserPQuota", ["pykotaUserName"], base=userquotaid, scope=ldap.SCOPE_BASE)
335        if result :
336            username = result[0][1]["pykotaUserName"][0]
337            result = self.doSearch("(&(objectClass=pykotaAccount)(|(pykotaUserName=%s)(%s=%s)))" % (username, self.info["userrdn"], username), ["pykotaLimitBy"])
338            if result :
339                return result[0][1]["pykotaLimitBy"][0]
340       
341    def getGroupLimitBy(self, groupquotaid) :   
342        """Returns the way in which group printing is limited."""
343        # first get the group's name from the group quota id
344        result = self.doSearch("objectClass=pykotaGroupPQuota", ["pykotaGroupName"], base=groupquotaid, scope=ldap.SCOPE_BASE)
345        if result :
346            groupname = result[0][1]["pykotaGroupName"][0]
347            result = self.doSearch("(&(objectClass=pykotaGroup)(|(pykotaGroupName=%s)(%s=%s)))" % (groupname, self.info["grouprdn"], groupname), ["pykotaLimitBy"])
348            if result :
349                return result[0][1]["pykotaLimitBy"][0]
350       
351    def setUserBalance(self, userid, balance) :   
352        """Sets the account balance for a given user to a fixed value."""
353        raise PyKotaStorageError, "Not implemented !"
354       
355    def limitUserBy(self, userid, limitby) :   
356        """Limits a given user based either on print quota or on account balance."""
357        raise PyKotaStorageError, "Not implemented !"
358       
359    def limitGroupBy(self, groupid, limitby) :   
360        """Limits a given group based either on print quota or on sum of its users' account balances."""
361        raise PyKotaStorageError, "Not implemented !"
362       
363    def setUserPQuota(self, userquotaid, printerid, softlimit, hardlimit) :
364        """Sets soft and hard limits for a user quota on a specific printer given (userid, printerid)."""
365        fields = { 
366                   "pykotaSoftLimit" : str(softlimit),
367                   "pykotaHardLimit" : str(hardlimit),
368                 } 
369        return self.doModify(userquotaid, fields)
370       
371    def setGroupPQuota(self, groupquotaid, printerid, softlimit, hardlimit) :
372        """Sets soft and hard limits for a group quota on a specific printer given (groupid, printerid)."""
373        fields = { 
374                   "pykotaSoftLimit" : str(softlimit),
375                   "pykotaHardLimit" : str(hardlimit),
376                 } 
377        return self.doModify(groupquotaid, fields)
378       
379    def resetUserPQuota(self, userid, printerid) :   
380        """Resets the page counter to zero for a user on a printer. Life time page counter is kept unchanged."""
381        raise PyKotaStorageError, "Not implemented !"
382       
383    def resetGroupPQuota(self, groupid, printerid) :   
384        """Resets the page counter to zero for a group on a printer. Life time page counter is kept unchanged."""
385        raise PyKotaStorageError, "Not implemented !"
386       
387    def updateUserPQuota(self, userid, printerid, pagecount) :
388        """Updates the used user Quota information given (userid, printerid) and a job size in pages."""
389        raise PyKotaStorageError, "Not implemented !"
390       
391    def getUserPQuota(self, userquotaid, printerid) :
392        """Returns the Print Quota information for a given (userquotaid, printerid)."""
393        # first get the user's name from the id
394        result = self.doSearch("objectClass=pykotaUserPQuota", ["pykotaUserName", "pykotaPageCounter", "pykotaLifePageCounter", "pykotaSoftLimit", "pykotaHardLimit", "pykotaDateLimit"], base=userquotaid, scope=ldap.SCOPE_BASE)
395        if result :
396            fields = result[0][1]
397            datelimit = fields["pykotaDateLimit"][0].strip()
398            if (not datelimit) or (datelimit.upper() == "NONE") : 
399                datelimit = None
400            return { "lifepagecounter" : int(fields["pykotaLifePageCounter"][0]), 
401                     "pagecounter" : int(fields["pykotaPageCounter"][0]),
402                     "softlimit" : int(fields["pykotaSoftLimit"][0]),
403                     "hardlimit" : int(fields["pykotaHardLimit"][0]),
404                     "datelimit" : datelimit
405                   }
406       
407    def getGroupPQuota(self, grouppquotaid, printerid) :
408        """Returns the Print Quota information for a given (grouppquotaid, printerid)."""
409        result = self.doSearch("objectClass=pykotaGroupPQuota", ["pykotaGroupName", "pykotaSoftLimit", "pykotaHardLimit", "pykotaDateLimit"], base=grouppquotaid, scope=ldap.SCOPE_BASE)
410        if result :
411            fields = result[0][1]
412            groupname = fields["pykotaGroupName"][0]
413            datelimit = fields["pykotaDateLimit"][0].strip()
414            if (not datelimit) or (datelimit.upper() == "NONE") : 
415                datelimit = None
416            quota = {
417                      "softlimit" : int(fields["pykotaSoftLimit"][0]),
418                      "hardlimit" : int(fields["pykotaHardLimit"][0]),
419                      "datelimit" : datelimit
420                    }
421            members = self.getGroupMembersNames(groupname)
422            pagecounter = lifepagecounter = 0
423            printerusers = self.getPrinterUsers(printerid)
424            if printerusers :
425                for (userid, username) in printerusers :
426                    if username in members :
427                        userpquota = self.getUserPQuota(userid, printerid)
428                        if userpquota :
429                            pagecounter += userpquota["pagecounter"]
430                            lifepagecounter += userpquota["lifepagecounter"]
431            quota.update({"pagecounter": pagecounter, "lifepagecounter": lifepagecounter})               
432            return quota
433       
434    def setUserDateLimit(self, userid, printerid, datelimit) :
435        """Sets the limit date for a soft limit to become an hard one given (userid, printerid)."""
436        raise PyKotaStorageError, "Not implemented !"
437       
438    def setGroupDateLimit(self, groupid, printerid, datelimit) :
439        """Sets the limit date for a soft limit to become an hard one given (groupid, printerid)."""
440        raise PyKotaStorageError, "Not implemented !"
441       
442    def addJobToHistory(self, jobid, userid, printerid, pagecounter, action) :
443        """Adds a job to the history: (jobid, userid, printerid, last page counter taken from requester)."""
444        raise PyKotaStorageError, "Not implemented !"
445   
446    def updateJobSizeInHistory(self, historyid, jobsize) :
447        """Updates a job size in the history given the history line's id."""
448        raise PyKotaStorageError, "Not implemented !"
449   
450    def getPrinterPageCounter(self, printerid) :
451        """Returns the last page counter value for a printer given its id, also returns last username, last jobid and history line id."""
452        result = self.doSearch("objectClass=pykotaPrinter", ["pykotaPrinterName", self.info["printerrdn"]], base=printerid, scope=ldap.SCOPE_BASE)
453        if result :
454            fields = result[0][1]
455            printername = (fields.get("pykotaPrinterName") or fields.get(self.info["printerrdn"]))[0]
456            result = self.doSearch("(&(objectClass=pykotaLastjob)(|(pykotaPrinterName=%s)(%s=%s)))" % (printername, self.info["printerrdn"], printername), ["pykotaLastJobIdent"], base=self.info["lastjobbase"])
457            if result :
458                lastjobident = result[0][1]["pykotaLastJobIdent"][0]
459                result = self.doSearch("(&(objectClass=pykotaJob)(cn=%s))" % lastjobident, ["pykotaUserName", "pykotaPrinterName", "pykotaJobId", "pykotaPrinterPageCounter", "pykotaJobSize", "pykotaAction", "createTimestamp"], base=self.info["jobbase"])
460                if result :
461                    fields = result[0][1]
462                    return { "id": lastjobident, 
463                             "jobid" : fields.get("pykotaJobId")[0],
464                             "userid" : self.getUserId(fields.get("pykotaUserName")[0]),
465                             "username" : fields.get("pykotaUserName")[0], 
466                             "pagecounter" : int(fields.get("pykotaPrinterPageCounter")[0]),
467                           }
468       
469    def addUserToGroup(self, userid, groupid) :   
470        """Adds an user to a group."""
471        raise PyKotaStorageError, "Not implemented !"
472       
473    def deleteUser(self, userid) :   
474        """Completely deletes an user from the Quota Storage."""
475        raise PyKotaStorageError, "Not implemented !"
476       
477    def deleteGroup(self, groupid) :   
478        """Completely deletes an user from the Quota Storage."""
479        raise PyKotaStorageError, "Not implemented !"
480       
481    def computePrinterJobPrice(self, printerid, jobsize) :   
482        """Returns the price for a job on a given printer."""
483        # TODO : create a base class with things like this
484        prices = self.getPrinterPrices(printerid)
485        if prices is None :
486            perpage = perjob = 0.0
487        else :   
488            (perpage, perjob) = prices
489        return perjob + (perpage * jobsize)
Note: See TracBrowser for help on using the browser.