root / pykota / trunk / pykota / storages / pgstorage.py @ 1079

Revision 1079, 21.8 kB (checked in by jalet, 21 years ago)

Email field added to PostgreSQL schema

  • 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.7  2003/07/09 20:17:07  jalet
24# Email field added to PostgreSQL schema
25#
26# Revision 1.6  2003/07/07 11:49:24  jalet
27# Lots of small fixes with the help of PyChecker
28#
29# Revision 1.5  2003/07/07 08:33:19  jalet
30# Bug fix due to a typo in LDAP code
31#
32# Revision 1.4  2003/06/30 13:54:21  jalet
33# Sorts by user / group name
34#
35# Revision 1.3  2003/06/25 14:10:01  jalet
36# Hey, it may work (edpykota --reset excepted) !
37#
38# Revision 1.2  2003/06/12 21:09:57  jalet
39# wrongly placed code.
40#
41# Revision 1.1  2003/06/10 16:37:54  jalet
42# Deletion of the second user which is not needed anymore.
43# Added a debug configuration field in /etc/pykota.conf
44# All queries can now be sent to the logger in debug mode, this will
45# greatly help improve performance when time for this will come.
46#
47#
48#
49#
50
51from pykota.storage import PyKotaStorageError
52from pykota.storage import StorageObject,StorageUser,StorageGroup,StoragePrinter,StorageLastJob,StorageUserPQuota,StorageGroupPQuota
53
54try :
55    import pg
56except ImportError :   
57    import sys
58    # TODO : to translate or not to translate ?
59    raise PyKotaStorageError, "This python version (%s) doesn't seem to have the PygreSQL module installed correctly." % sys.version.split()[0]
60
61class Storage :
62    def __init__(self, pykotatool, host, dbname, user, passwd) :
63        """Opens the PostgreSQL database connection."""
64        self.tool = pykotatool
65        self.debug = pykotatool.config.getDebug()
66        self.closed = 1
67        try :
68            (host, port) = host.split(":")
69            port = int(port)
70        except ValueError :   
71            port = -1         # Use PostgreSQL's default tcp/ip port (5432).
72       
73        try :
74            self.database = pg.connect(host=host, port=port, dbname=dbname, user=user, passwd=passwd)
75        except pg.error, msg :
76            raise PyKotaStorageError, msg
77        else :   
78            self.closed = 0
79            if self.debug :
80                self.tool.logger.log_message("Database opened (host=%s, port=%s, dbname=%s, user=%s)" % (host, port, dbname, user), "debug")
81           
82    def __del__(self) :       
83        """Closes the database connection."""
84        if not self.closed :
85            self.database.close()
86            self.closed = 1
87            if self.debug :
88                self.tool.logger.log_message("Database closed.", "debug")
89       
90    def beginTransaction(self) :   
91        """Starts a transaction."""
92        self.database.query("BEGIN;")
93        if self.debug :
94            self.tool.logger.log_message("Transaction begins...", "debug")
95       
96    def commitTransaction(self) :   
97        """Commits a transaction."""
98        self.database.query("COMMIT;")
99        if self.debug :
100            self.tool.logger.log_message("Transaction committed.", "debug")
101       
102    def rollbackTransaction(self) :     
103        """Rollbacks a transaction."""
104        self.database.query("ROLLBACK;")
105        if self.debug :
106            self.tool.logger.log_message("Transaction aborted.", "debug")
107       
108    def doSearch(self, query) :
109        """Does a search query."""
110        query = query.strip()   
111        if not query.endswith(';') :   
112            query += ';'
113        try :
114            if self.debug :
115                self.tool.logger.log_message("QUERY : %s" % query, "debug")
116            result = self.database.query(query)
117        except pg.error, msg :   
118            raise PyKotaStorageError, msg
119        else :   
120            if (result is not None) and (result.ntuples() > 0) : 
121                return result.dictresult()
122           
123    def doModify(self, query) :
124        """Does a (possibly multiple) modify query."""
125        query = query.strip()   
126        if not query.endswith(';') :   
127            query += ';'
128        try :
129            if self.debug :
130                self.tool.logger.log_message("QUERY : %s" % query, "debug")
131            result = self.database.query(query)
132        except pg.error, msg :   
133            raise PyKotaStorageError, msg
134        else :   
135            return result
136           
137    def doQuote(self, field) :
138        """Quotes a field for use as a string in SQL queries."""
139        if type(field) == type(0.0) : 
140            typ = "decimal"
141        elif type(field) == type(0) :   
142            typ = "int"
143        else :   
144            typ = "text"
145        return pg._quote(field, typ)
146       
147    def getUser(self, username) :   
148        """Extracts user information given its name."""
149        user = StorageUser(self, username)
150        result = self.doSearch("SELECT * FROM users WHERE username=%s LIMIT 1" % self.doQuote(username))
151        if result :
152            fields = result[0]
153            user.ident = fields.get("id")
154            user.LimitBy = fields.get("limitby")
155            user.AccountBalance = fields.get("balance")
156            user.LifeTimePaid = fields.get("lifetimepaid")
157            user.Email = fields.get("email")
158            user.Exists = 1
159        return user
160       
161    def getGroup(self, groupname) :   
162        """Extracts group information given its name."""
163        group = StorageGroup(self, groupname)
164        result = self.doSearch("SELECT * FROM groups WHERE groupname=%s LIMIT 1" % self.doQuote(groupname))
165        if result :
166            fields = result[0]
167            group.ident = fields.get("id")
168            group.LimitBy = fields.get("limitby")
169            result = self.doSearch("SELECT SUM(balance) AS balance, SUM(lifetimepaid) AS lifetimepaid FROM users WHERE id IN (SELECT userid FROM groupsmembers WHERE groupid=%s)" % self.doQuote(group.ident))
170            if result :
171                fields = result[0]
172                group.AccountBalance = fields.get("balance")
173                group.LifeTimePaid = fields.get("lifetimepaid")
174            group.Exists = 1
175        return group
176       
177    def getPrinter(self, printername) :       
178        """Extracts printer information given its name."""
179        printer = StoragePrinter(self, printername)
180        result = self.doSearch("SELECT * FROM printers WHERE printername=%s LIMIT 1" % self.doQuote(printername))
181        if result :
182            fields = result[0]
183            printer.ident = fields.get("id")
184            printer.PricePerJob = fields.get("priceperjob")
185            printer.PricePerPage = fields.get("priceperpage")
186            printer.LastJob = self.getPrinterLastJob(printer)
187            printer.Exists = 1
188        return printer   
189           
190    def getUserGroups(self, user) :       
191        """Returns the user's groups list."""
192        groups = []
193        result = self.doSearch("SELECT groupname FROM groupsmembers JOIN groups ON groupsmembers.groupid=groups.id WHERE userid=%s" % self.doQuote(user.ident))
194        if result :
195            for record in result :
196                groups.append(self.getGroup(record.get("groupname")))
197        return groups       
198       
199    def getGroupMembers(self, group) :       
200        """Returns the group's members list."""
201        groupmembers = []
202        result = self.doSearch("SELECT * FROM groupsmembers JOIN users ON groupsmembers.userid=users.id WHERE groupid=%s" % self.doQuote(group.ident))
203        if result :
204            for record in result :
205                user = StorageUser(self, record.get("username"))
206                user.ident = record.get("userid")
207                user.LimitBy = record.get("limitby")
208                user.AccountBalance = record.get("balance")
209                user.LifeTimePaid = record.get("lifetimepaid")
210                user.Email = record.get("email")
211                user.Exists = 1
212                groupmembers.append(user)
213        return groupmembers       
214       
215    def getUserPQuota(self, user, printer) :       
216        """Extracts a user print quota."""
217        userpquota = StorageUserPQuota(self, user, printer)
218        if user.Exists :
219            result = self.doSearch("SELECT id, lifepagecounter, pagecounter, softlimit, hardlimit, datelimit FROM userpquota WHERE userid=%s AND printerid=%s" % (self.doQuote(user.ident), self.doQuote(printer.ident)))
220            if result :
221                fields = result[0]
222                userpquota.ident = fields.get("id")
223                userpquota.PageCounter = fields.get("pagecounter")
224                userpquota.LifePageCounter = fields.get("lifepagecounter")
225                userpquota.SoftLimit = fields.get("softlimit")
226                userpquota.HardLimit = fields.get("hardlimit")
227                userpquota.DateLimit = fields.get("datelimit")
228                userpquota.Exists = 1
229        return userpquota
230       
231    def getGroupPQuota(self, group, printer) :       
232        """Extracts a group print quota."""
233        grouppquota = StorageGroupPQuota(self, group, printer)
234        if group.Exists :
235            result = self.doSearch("SELECT id, softlimit, hardlimit, datelimit FROM grouppquota WHERE groupid=%s AND printerid=%s" % (self.doQuote(group.ident), self.doQuote(printer.ident)))
236            if result :
237                fields = result[0]
238                grouppquota.ident = fields.get("id")
239                grouppquota.SoftLimit = fields.get("softlimit")
240                grouppquota.HardLimit = fields.get("hardlimit")
241                grouppquota.DateLimit = fields.get("datelimit")
242                result = self.doSearch("SELECT SUM(lifepagecounter) AS lifepagecounter, SUM(pagecounter) AS pagecounter FROM userpquota WHERE printerid=%s AND userid IN (SELECT userid FROM groupsmembers WHERE groupid=%s)" % (self.doQuote(printer.ident), self.doQuote(group.ident)))
243                if result :
244                    fields = result[0]
245                    grouppquota.PageCounter = fields.get("pagecounter")
246                    grouppquota.LifePageCounter = fields.get("lifepagecounter")
247                grouppquota.Exists = 1
248        return grouppquota
249       
250    def getPrinterLastJob(self, printer) :       
251        """Extracts a printer's last job information."""
252        lastjob = StorageLastJob(self, printer)
253        result = self.doSearch("SELECT jobhistory.id, jobid, userid, username, pagecounter, jobsize, jobdate FROM jobhistory, users WHERE printerid=%s AND userid=users.id ORDER BY jobdate DESC LIMIT 1" % self.doQuote(printer.ident))
254        if result :
255            fields = result[0]
256            lastjob.ident = fields.get("id")
257            lastjob.JobId = fields.get("jobid")
258            lastjob.User = self.getUser(fields.get("username"))
259            lastjob.PrinterPageCounter = fields.get("pagecounter")
260            lastjob.JobSize = fields.get("jobsize")
261            lastjob.JobAction = fields.get("action")
262            lastjob.JobDate = fields.get("jobdate")
263            lastjob.Exists = 1
264        return lastjob
265       
266    def getMatchingPrinters(self, printerpattern) :
267        """Returns the list of all printers for which name matches a certain pattern."""
268        printers = []
269        # We 'could' do a SELECT printername FROM printers WHERE printername LIKE ...
270        # but we don't because other storages semantics may be different, so every
271        # storage should use fnmatch to match patterns and be storage agnostic
272        result = self.doSearch("SELECT * FROM printers")
273        if result :
274            for record in result :
275                if self.tool.matchString(record["printername"], [ printerpattern ]) :
276                    printer = StoragePrinter(self, record["printername"])
277                    printer.ident = record.get("id")
278                    printer.PricePerJob = record.get("priceperjob")
279                    printer.PricePerPage = record.get("priceperpage")
280                    printer.LastJob = self.getPrinterLastJob(printer)
281                    printer.Exists = 1
282                    printers.append(printer)
283        return printers       
284       
285    def getPrinterUsersAndQuotas(self, printer, names=None) :       
286        """Returns the list of users who uses a given printer, along with their quotas."""
287        usersandquotas = []
288        result = self.doSearch("SELECT users.id as uid,username,balance,lifetimepaid,limitby,email,userpquota.id,lifepagecounter,pagecounter,softlimit,hardlimit,datelimit FROM users JOIN userpquota ON users.id=userpquota.userid AND printerid=%s ORDER BY username ASC" % self.doQuote(printer.ident))
289        if result :
290            for record in result :
291                user = StorageUser(self, record.get("username"))
292                if (names is None) or self.tool.matchString(user.Name, names) :
293                    user.ident = record.get("uid")
294                    user.LimitBy = record.get("limitby")
295                    user.AccountBalance = record.get("balance")
296                    user.LifeTimePaid = record.get("lifetimepaid")
297                    user.Email = record.get("email") 
298                    user.Exists = 1
299                    userpquota = StorageUserPQuota(self, user, printer)
300                    userpquota.ident = record.get("id")
301                    userpquota.PageCounter = record.get("pagecounter")
302                    userpquota.LifePageCounter = record.get("lifepagecounter")
303                    userpquota.SoftLimit = record.get("softlimit")
304                    userpquota.HardLimit = record.get("hardlimit")
305                    userpquota.DateLimit = record.get("datelimit")
306                    userpquota.Exists = 1
307                    usersandquotas.append((user, userpquota))
308        return usersandquotas
309               
310    def getPrinterGroupsAndQuotas(self, printer, names=None) :       
311        """Returns the list of groups which uses a given printer, along with their quotas."""
312        groupsandquotas = []
313        result = self.doSearch("SELECT groupname FROM groups JOIN grouppquota ON groups.id=grouppquota.groupid AND printerid=%s ORDER BY groupname ASC" % self.doQuote(printer.ident))
314        if result :
315            for record in result :
316                group = self.getGroup(record.get("groupname"))
317                if (names is None) or self.tool.matchString(group.Name, names) :
318                    grouppquota = self.getGroupPQuota(group, printer)
319                    groupsandquotas.append((group, grouppquota))
320        return groupsandquotas
321       
322    def addPrinter(self, printername) :       
323        """Adds a printer to the quota storage, returns it."""
324        self.doModify("INSERT INTO printers (printername) VALUES (%s)" % self.doQuote(printername))
325        return self.getPrinter(printername)
326       
327    def addUser(self, user) :       
328        """Adds a user to the quota storage, returns its id."""
329        self.doModify("INSERT INTO users (username, limitby, balance, lifetimepaid) VALUES (%s, %s, %s, %s)" % (self.doQuote(user.Name), self.doQuote(user.LimitBy), self.doQuote(user.AccountBalance), self.doQuote(user.LifeTimePaid)))
330        return self.getUser(user.Name)
331       
332    def addGroup(self, group) :       
333        """Adds a group to the quota storage, returns its id."""
334        self.doModify("INSERT INTO groups (groupname, limitby) VALUES (%s, %s)" % (self.doQuote(group.Name), self.doQuote(group.LimitBy)))
335        return self.getGroup(group.Name)
336
337    def addUserToGroup(self, user, group) :   
338        """Adds an user to a group."""
339        result = self.doSearch("SELECT COUNT(*) AS mexists FROM groupsmembers WHERE groupid=%s AND userid=%s" % (self.doQuote(group.ident), self.doQuote(user.ident)))
340        try :
341            mexists = int(result[0].get("mexists"))
342        except (IndexError, TypeError) :   
343            mexists = 0
344        if not mexists :   
345            self.doModify("INSERT INTO groupsmembers (groupid, userid) VALUES (%s, %s)" % (self.doQuote(group.ident), self.doQuote(user.ident)))
346           
347    def addUserPQuota(self, user, printer) :
348        """Initializes a user print quota on a printer."""
349        self.doModify("INSERT INTO userpquota (userid, printerid) VALUES (%s, %s)" % (self.doQuote(user.ident), self.doQuote(printer.ident)))
350        return self.getUserPQuota(user, printer)
351       
352    def addGroupPQuota(self, group, printer) :
353        """Initializes a group print quota on a printer."""
354        self.doModify("INSERT INTO grouppquota (groupid, printerid) VALUES (%s, %s)" % (self.doQuote(group.ident), self.doQuote(printer.ident)))
355        return self.getGroupPQuota(group, printer)
356       
357    def writePrinterPrices(self, printer) :   
358        """Write the printer's prices back into the storage."""
359        self.doModify("UPDATE printers SET priceperpage=%s, priceperjob=%s WHERE printerid=%s" % (self.doQuote(printer.PricePerPage), self.doQuote(printer.PricePerJob), self.doQuote(printer.ident)))
360       
361    def writeUserLimitBy(self, user, limitby) :   
362        """Sets the user's limiting factor."""
363        self.doModify("UPDATE users SET limitby=%s WHERE id=%s" % (self.doQuote(limitby), self.doQuote(user.ident)))
364       
365    def writeGroupLimitBy(self, group, limitby) :   
366        """Sets the group's limiting factor."""
367        self.doModify("UPDATE groups SET limitby=%s WHERE id=%s" % (self.doQuote(limitby), self.doQuote(group.ident)))
368       
369    def writeUserPQuotaDateLimit(self, userpquota, datelimit) :   
370        """Sets the date limit permanently for a user print quota."""
371        self.doModify("UPDATE userpquota SET datelimit::TIMESTAMP=%s WHERE id=%s" % (self.doQuote(datelimit), self.doQuote(userpquota.ident)))
372           
373    def writeGroupPQuotaDateLimit(self, grouppquota, datelimit) :   
374        """Sets the date limit permanently for a group print quota."""
375        self.doModify("UPDATE grouppquota SET datelimit::TIMESTAMP=%s WHERE id=%s" % (self.doQuote(datelimit), self.doQuote(grouppquota.ident)))
376       
377    def writeUserPQuotaPagesCounters(self, userpquota, newpagecounter, newlifepagecounter) :   
378       """Sets the new page counters permanently for a user print quota."""
379       self.doModify("UPDATE userpquota SET pagecounter=%s,lifepagecounter=%s WHERE id=%s" % (self.doQuote(newpagecounter), self.doQuote(newlifepagecounter), self.doQuote(userpquota.ident)))
380       
381    def writeUserAccountBalance(self, user, newbalance, newlifetimepaid=None) :   
382       """Sets the new account balance and eventually new lifetime paid."""
383       if newlifetimepaid is not None :
384           self.doModify("UPDATE users SET balance=%s, lifetimepaid=%s WHERE id=%s" % (self.doQuote(newbalance), self.doQuote(newlifetimepaid), self.doQuote(user.ident)))
385       else :   
386           self.doModify("UPDATE users SET balance=%s WHERE id=%s" % (self.doQuote(newbalance), self.doQuote(user.ident)))
387           
388    def writeLastJobSize(self, lastjob, jobsize) :       
389        """Sets the last job's size permanently."""
390        self.doModify("UPDATE jobhistory SET jobsize=%s WHERE id=%s" % (self.doQuote(jobsize), self.doQuote(lastjob.ident)))
391       
392    def writeJobNew(self, printer, user, jobid, pagecounter, action, jobsize=None) :   
393        """Adds a job in a printer's history."""
394        if jobsize is not None :
395            self.doModify("INSERT INTO jobhistory (userid, printerid, jobid, pagecounter, action, jobsize) VALUES (%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)))
396        else :   
397            self.doModify("INSERT INTO jobhistory (userid, printerid, jobid, pagecounter, action) VALUES (%s, %s, %s, %s, %s)" % (self.doQuote(user.ident), self.doQuote(printer.ident), self.doQuote(jobid), self.doQuote(pagecounter), self.doQuote(action)))
398           
399    def writeUserPQuotaLimits(self, userpquota, softlimit, hardlimit) :
400        """Sets soft and hard limits for a user quota."""
401        self.doModify("UPDATE userpquota SET softlimit=%s, hardlimit=%s, datelimit=NULL WHERE id=%s" % (self.doQuote(softlimit), self.doQuote(hardlimit), self.doQuote(userpquota.ident)))
402       
403    def writeGroupPQuotaLimits(self, grouppquota, softlimit, hardlimit) :
404        """Sets soft and hard limits for a group quota on a specific printer given (groupid, printerid)."""
405        self.doModify("UPDATE grouppquota SET softlimit=%s, hardlimit=%s, datelimit=NULL WHERE id=%s" % (self.doQuote(softlimit), self.doQuote(hardlimit), self.doQuote(grouppquota.ident)))
406
407    def deleteUser(self, user) :   
408        """Completely deletes an user from the Quota Storage."""
409        # TODO : What should we do if we delete the last person who used a given printer ?
410        # TODO : we can't reassign the last job to the previous one, because next user would be
411        # TODO : incorrectly charged (overcharged).
412        for q in [ 
413                    "DELETE FROM groupsmembers WHERE userid=%s" % self.doQuote(user.ident),
414                    "DELETE FROM jobhistory WHERE userid=%s" % self.doQuote(user.ident),
415                    "DELETE FROM userpquota WHERE userid=%s" % self.doQuote(user.ident),
416                    "DELETE FROM users WHERE id=%s" % self.doQuote(user.ident),
417                  ] :
418            self.doModify(q)
419       
420    def deleteGroup(self, group) :   
421        """Completely deletes a group from the Quota Storage."""
422        for q in [
423                   "DELETE FROM groupsmembers WHERE groupid=%s" % self.doQuote(group.ident),
424                   "DELETE FROM grouppquota WHERE groupid=%s" % self.doQuote(group.ident),
425                   "DELETE FROM groups WHERE id=%s" % self.doQuote(group.ident),
426                 ] : 
427            self.doModify(q)
428       
Note: See TracBrowser for help on using the browser.