root / pykota / trunk / bin / pkusers @ 2829

Revision 2829, 20.7 kB (checked in by jerome, 17 years ago)

Did a pass with pylint.

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