root / pykota / trunk / bin / pkusers @ 3080

Revision 3052, 21.5 kB (checked in by jerome, 18 years ago)

Now pkprinters reroutes CUPS print queues through PyKota or
through CUPS only when adding or deleting printers in the
database.
Fixed some display uglyness in case no entry was found.

  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
Line 
1#! /usr/bin/env python
2# -*- coding: ISO-8859-15 -*-
3
4# PyKota Users Manager
5#
6# PyKota - Print Quotas for CUPS and LPRng
7#
8# (c) 2003, 2004, 2005, 2006 Jerome Alet <alet@librelogiciel.com>
9# This program is free software; you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation; either version 2 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program; if not, write to the Free Software
21# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22#
23# $Id$
24#
25#
26
27import sys
28import pwd
29import grp
30
31from pykota.tool import Percent, PyKotaTool, PyKotaCommandLineError, crashed, N_
32from pykota.storage import StorageUser, StorageGroup
33
34__doc__ = N_("""pkusers v%(__version__)s (c) %(__years__)s %(__author__)s
35
36An Users and Groups Manager for PyKota.
37
38command line usage :
39
40  pkusers [options] user1 user2 user3 ... userN
41 
42or : 
43
44  pkusers --groups [options] group1 group2 group3 ... groupN
45
46options :
47
48  -v | --version       Prints pkusers's version number then exits.
49  -h | --help          Prints this message then exits.
50 
51  -a | --add           Adds users if they don't exist on the database.
52                       If they exist, they are modified unless
53                       -s|--skipexisting is also used.
54                       
55  -d | --delete        Deletes users from the quota storage.
56
57  -e | --email addr    Sets the email address for the users.
58                       If the addr parameter begins with @, then
59                       the username is prepended to addr to form
60                       a valid email address.
61
62  -D | --description d Adds a textual description to users or groups.
63                       
64  -g | --groups        Edit users groups instead of users.
65                         
66  -o | --overcharge f  Sets the overcharging factor applied to the user
67                       when computing the cost of a print job. Positive or
68                       negative floating point values are allowed,
69                       this allows you to do some really creative
70                       things like giving money to an user whenever
71                       he prints. The number of pages in a print job
72                       is not modified by this coefficient, only the
73                       cost of the job for a particular user.
74                       Only users have such a coefficient.
75 
76  -i | --ingroups g1[,g2...]  Puts the users into each of the groups
77                              listed, separated by commas. The groups
78                              must already exist in the Quota Storage.
79                       
80  -L | --list          Lists users or groups.
81 
82  -l | --limitby l     Choose if the user/group is limited in printing                     
83                       by its account balance or by its page quota.
84                       The default value is 'quota'. Allowed values
85                       are 'quota' 'balance' 'noquota' 'noprint' 
86                       and 'nochange' :
87                       
88                         - quota : limit by number of pages per printer.
89                         - balance : limit by number of credits in account.
90                         - noquota : no limit, accounting still done.
91                         - nochange : no limit, accounting not done.
92                         - noprint : printing is denied.
93                       NB : nochange and noprint are not supported for groups.
94                       
95  -b | --balance b     Sets the user's account balance to b.                     
96                       Account balance may be increase or decreased
97                       if b is prefixed with + or -.
98                       WARNING : when decreasing account balance,
99                       the total paid so far by the user is decreased
100                       too.
101                       Groups don't have a real balance, but the
102                       sum of their users' account balance.
103                       
104  -C | --comment txt   Defines some informational text to be associated
105                       with a change to an user's account balance.
106                       Only meaningful if -b | --balance is also used.
107                       
108                       
109  -r | --remove        In combination with the --ingroups option above,                       
110                       remove users from the specified users groups.
111                       
112  -s | --skipexisting  In combination with the --add option above, tells
113                       pkusers to not modify existing users.
114                       
115  user1 through userN and group1 through groupN can use wildcards
116  if the --add option is not set.
117 
118examples :                             
119
120  $ pkusers --add john paul george ringo/ringo@example.com
121 
122  This will add users john, paul, george and ringo to the quota
123  database. User ringo's email address will also be set to
124  'ringo@example.com'
125 
126  $ pkusers --ingroups coders,it jerome
127 
128  User jerome is put into the groups "coders" and "it" which must
129  already exist in the quota database.
130           
131  $ pkusers --limitby balance jerome
132 
133  This will tell PyKota to limit jerome by his account's balance
134  when printing.
135 
136  $ pkusers --balance +10.0 --comment "He paid with his blood !" jerome
137 
138  This will increase jerome's account balance by 10.0 (in your
139  own currency). You can decrease the account balance with a
140  dash prefix, and set it to a fixed amount with no prefix.
141  A comment will be stored for this balance change.
142 
143  $ pkusers --delete jerome rachel
144 
145  This will completely delete jerome and rachel from the quota
146  database. All their quotas and jobs will be deleted too.
147 
148  $ pkusers --overcharge 2.5 poorstudent
149 
150  This will overcharge the poorstudent user by a factor of 2.5.
151 
152  $ pkusers --overcharge -1 jerome
153 
154  User jerome will actually earn money whenever he prints.
155 
156  $ pkusers --overcharge 0 boss
157 
158  User boss can print at will, it won't cost him anything because the
159  cost of each print job will be multiplied by zero before charging
160  his account.
161
162  $ pkusers --email @example.com
163
164  This will set the email address for each user to username@example.com
165""")
166       
167class PKUsers(PyKotaTool) :       
168    """A class for a users and users groups manager."""
169    def modifyEntry(self, entry, groups, limitby, description, overcharge=None, balance=None, balancevalue=None, comment=None, email=None) :
170        """Modifies an entry."""
171        if description is not None : # NB : "" is allowed !
172            entry.setDescription(description)
173        if limitby :   
174            entry.setLimitBy(limitby)
175        if not groups :
176            if email is not None :      # we allow "" to empty the field
177                if email.startswith("@") :
178                    email = "%s%s" % (entry.Name, email)
179                if email and email.count('@') != 1 :
180                    raise PyKotaCommandLineError, _("Invalid email address %s") % email
181                entry.setEmail(email)
182            if overcharge is not None : # NB : 0 is allowed !     
183                entry.setOverChargeFactor(overcharge)
184            if balance :
185                if balance.startswith("+") or balance.startswith("-") :
186                    newbalance = float(entry.AccountBalance or 0.0) + balancevalue
187                    newlifetimepaid = float(entry.LifeTimePaid or 0.0) + balancevalue
188                    entry.setAccountBalance(newbalance, newlifetimepaid, comment)
189                else :
190                    diff = balancevalue - float(entry.AccountBalance or 0.0)
191                    newlifetimepaid = float(entry.LifeTimePaid or 0.0) + diff
192                    entry.setAccountBalance(balancevalue, newlifetimepaid, comment)
193                   
194    def manageUsersGroups(self, ugroups, user, remove) :       
195        """Manage user group membership."""
196        for ugroup in ugroups :
197            if remove :
198                ugroup.delUserFromGroup(user)
199            else :
200                ugroup.addUserToGroup(user)
201               
202    def main(self, names, options) :
203        """Manage users or groups."""
204        names = self.sanitizeNames(options, names)
205        suffix = (options["groups"] and "Group") or "User"       
206       
207        if not options["list"] :
208            percent = Percent(self)
209           
210        if not options["add"] :
211            if not options["list"] :
212                percent.display("%s..." % _("Extracting datas"))
213            if not names :      # NB : can't happen for --delete because it's catched earlier
214                names = ["*"]
215            entries = getattr(self.storage, "getMatching%ss" % suffix)(",".join(names))
216            if not entries :
217                if not options["list"] :
218                    percent.display("\n")
219                raise PyKotaCommandLineError, _("There's no %s matching %s") % (_(suffix.lower()), " ".join(names))
220            if not options["list"] :   
221                percent.setSize(len(entries))
222               
223        if options["list"] :
224            if suffix == "User" :
225                maildomain = self.config.getMailDomain()
226                smtpserver = self.config.getSMTPServer()
227                for entry in entries :
228                    email = entry.Email
229                    if not email :
230                        if maildomain :     
231                            email = "%s@%s" % (entry.Name, maildomain)
232                        elif smtpserver :   
233                            email = "%s@%s" % (entry.Name, smtpserver)
234                        else :   
235                            email = "%s@%s" % (entry.Name, "localhost")
236                    msg = "%s - <%s>" % (entry.Name, email)
237                    if entry.Description :
238                        msg += " - %s" % entry.Description
239                    print msg   
240                    print "    %s" % (_("Limited by : %s") % entry.LimitBy)
241                    print "    %s" % (_("Account balance : %.2f") % (entry.AccountBalance or 0.0))
242                    print "    %s" % (_("Total paid so far : %.2f") % (entry.LifeTimePaid or 0.0))
243                    print "    %s" % (_("Overcharging factor : %.2f") % entry.OverCharge)
244                    print
245            else :   
246                for entry in entries :
247                    msg = "%s" % entry.Name
248                    if entry.Description :
249                        msg += " - %s" % entry.Description
250                    print msg   
251                    print "    %s" % (_("Limited by : %s") % entry.LimitBy)
252                    print "    %s" % (_("Group balance : %.2f") % (entry.AccountBalance or 0.0))
253                    print "    %s" % (_("Total paid so far : %.2f") % (entry.LifeTimePaid or 0.0))
254                    print
255        elif options["delete"] :   
256            percent.display("\n%s..." % _("Deletion"))
257            getattr(self.storage, "deleteMany%ss" % suffix)(entries)
258            percent.display("\n")
259        else :
260            limitby = options["limitby"]
261            if limitby :
262                limitby = limitby.strip().lower()
263            if limitby :
264                if limitby not in ('quota', 'balance', 'noquota', \
265                                            'noprint', 'nochange') :
266                    raise PyKotaCommandLineError, _("Invalid limitby value %s") % options["limitby"]
267                if (limitby in ('nochange', 'noprint')) and options["groups"] :   
268                    raise PyKotaCommandLineError, _("Invalid limitby value %s") % options["limitby"]
269               
270            overcharge = options["overcharge"]
271            if overcharge :
272                try :
273                    overcharge = float(overcharge.strip())
274                except (ValueError, AttributeError) :   
275                    raise PyKotaCommandLineError, _("Invalid overcharge value %s") % options["overcharge"]
276                   
277            balance = options["balance"]
278            if balance :
279                balance = balance.strip()
280                try :
281                    balancevalue = float(balance)
282                except ValueError :   
283                    raise PyKotaCommandLineError, _("Invalid balance value %s") % options["balance"]
284            else :   
285                balancevalue = None
286               
287            if options["ingroups"] :
288                usersgroups = self.storage.getMatchingGroups(options["ingroups"])
289                if not usersgroups :
290                    raise PyKotaCommandLineError, _("There's no users group matching %s") % " ".join(options["ingroups"].split(','))
291            else :         
292                usersgroups = []
293                   
294            description = options["description"]
295            if description :
296                description = description.strip()
297               
298            comment = options["comment"]
299            if comment :
300                comment = comment.strip()
301            email = options["email"]   
302            if email :
303                email = email.strip()
304            skipexisting = options["skipexisting"]   
305            groups = options["groups"]
306            remove = options["remove"]
307            self.storage.beginTransaction()
308            try :   
309                if options["add"] :   
310                    rejectunknown = self.config.getRejectUnknown()   
311                    percent.display("%s...\n" % _("Creation"))
312                    percent.setSize(len(names))
313                    for ename in names :
314                        useremail = None
315                        if not groups :
316                            splitname = ename.split('/', 1)     # username/email
317                            if len(splitname) == 1 :
318                                splitname.append("")
319                            (ename, useremail) = splitname
320                        if self.isValidName(ename) :
321                            reject = 0
322                            if rejectunknown :
323                                if groups :
324                                    try :
325                                        grp.getgrnam(ename)
326                                    except KeyError :   
327                                        self.printInfo(_("Unknown group %s") % ename, "error")
328                                        reject = 1
329                                else :   
330                                    try :
331                                        pwd.getpwnam(ename)
332                                    except KeyError :   
333                                        self.printInfo(_("Unknown user %s") % ename, "error")
334                                        reject = 1
335                            if not reject :       
336                                entry = globals()["Storage%s" % suffix](self.storage, ename)
337                                if groups :
338                                    self.modifyEntry(entry, groups, limitby, \
339                                                     description)
340                                else :   
341                                    self.modifyEntry(entry, groups, limitby, \
342                                                     description, overcharge,\
343                                                     balance, balancevalue, \
344                                                     comment, useremail or email)
345                                oldentry = getattr(self.storage, "add%s" % suffix)(entry)
346                                if oldentry is not None :
347                                    if skipexisting :
348                                        self.logdebug(_("%s %s already exists, skipping.") % (_(suffix), ename))
349                                    else :   
350                                        self.logdebug(_("%s %s already exists, will be modified.") % (_(suffix), ename))
351                                        if groups :
352                                            self.modifyEntry(oldentry, groups, \
353                                                     limitby, description)
354                                        else :
355                                            self.modifyEntry(oldentry, groups, limitby, \
356                                                     description, overcharge,\
357                                                     balance, balancevalue, \
358                                                     comment, useremail or email)
359                                        oldentry.save()
360                                        if not groups :
361                                            self.manageUsersGroups(usersgroups, oldentry, remove)
362                                elif usersgroups and not groups :
363                                    self.manageUsersGroups(usersgroups, \
364                                                           self.storage.getUser(ename), \
365                                                           remove)
366                        else :
367                            raise PyKotaCommandLineError, _("Invalid name %s") % ename
368                        percent.oneMore()
369                else :
370                    percent.display("\n%s...\n" % _("Modification"))
371                    for entry in entries :
372                        if groups :
373                            self.modifyEntry(entry, groups, limitby, description)
374                        else :   
375                            self.modifyEntry(entry, groups, limitby, description, \
376                                             overcharge, balance, balancevalue, \
377                                             comment, email)
378                            self.manageUsersGroups(usersgroups, entry, remove)               
379                        entry.save()   
380                        percent.oneMore()
381            except :                   
382                self.storage.rollbackTransaction()
383                raise
384            else :   
385                self.storage.commitTransaction()
386               
387        if not options["list"] :
388            percent.done()
389                     
390if __name__ == "__main__" : 
391    retcode = 0
392    try :
393        defaults = { \
394                     "comment" : "", \
395                   }
396        short_options = "hvaD:dgl:rso:i:b:C:Le:"
397        long_options = ["help", "version", "add", "description=", \
398                        "delete", "groups", "list", "remove", \
399                        "skipexisting", "overcharge=", "email=", \
400                        "ingroups=", "limitby=", "balance=", "comment=", \
401                       ]
402                       
403       
404        # Initializes the command line tool
405        manager = PKUsers(doc=__doc__)
406        manager.deferredInit()
407       
408        # parse and checks the command line
409        (options, args) = manager.parseCommandline(sys.argv[1:], short_options, long_options)
410       
411        # sets long options
412        options["help"] = options["h"] or options["help"]
413        options["version"] = options["v"] or options["version"]
414        options["add"] = options["a"] or options["add"]
415        options["description"] = options["D"] or options["description"]
416        options["delete"] = options["d"] or options["delete"] 
417        options["groups"] = options["g"] or options["groups"]
418        options["list"] = options["L"] or options["list"]
419        options["remove"] = options["r"] or options["remove"]
420        options["skipexisting"] = options["s"] or options["skipexisting"]
421        options["limitby"] = options["l"] or options["limitby"]
422        options["balance"] = options["b"] or options["balance"] 
423        options["ingroups"] = options["i"] or options["ingroups"]
424        options["overcharge"] = options["o"] or options["overcharge"]
425        options["comment"] = options["C"] or options["comment"] or defaults["comment"]
426        options["email"] = options["e"] or options["email"]
427       
428        if options["help"] :
429            manager.display_usage_and_quit()
430        elif options["version"] :
431            manager.display_version_and_quit()
432        elif (options["delete"] and (options["add"] or options["remove"] or options["description"] or options["email"])) \
433           or (options["skipexisting"] and not options["add"]) \
434           or (options["list"] and (options["add"] or options["delete"] or options["remove"] or options["description"] or options["email"])) \
435           or (options["groups"] and (options["balance"] or options["ingroups"] or options["overcharge"])) :
436            raise PyKotaCommandLineError, _("incompatible options, see help.")
437        elif options["remove"] and not options["ingroups"] :   
438            raise PyKotaCommandLineError, _("You have to pass user groups names on the command line")
439        elif (not args) and (options["add"] or options["delete"]) :
440            raise PyKotaCommandLineError, _("You have to pass user or group names on the command line")
441        else :
442            retcode = manager.main(args, options)
443    except KeyboardInterrupt :       
444        sys.stderr.write("\nInterrupted with Ctrl+C !\n")
445        retcode = -3
446    except PyKotaCommandLineError, msg :   
447        sys.stderr.write("%s : %s\n" % (sys.argv[0], msg))
448        retcode = -2
449    except SystemExit :       
450        pass
451    except :
452        try :
453            manager.crashed("pkusers failed")
454        except :   
455            crashed("pkusers failed")
456        retcode = -1
457
458    try :
459        manager.storage.close()
460    except (TypeError, NameError, AttributeError) :   
461        pass
462       
463    sys.exit(retcode)   
Note: See TracBrowser for help on using the browser.