root / pykota / trunk / bin / pkusers @ 2719

Revision 2719, 19.8 kB (checked in by jerome, 18 years ago)

pkusers --list was not documented.

  • 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 os
28import sys
29import pwd
30import grp
31
32from pykota.tool import PyKotaTool, PyKotaToolError, PyKotaCommandLineError, crashed, N_
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 --groups 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 main(self, names, options) :
161        """Manage users or groups."""
162        if not self.config.isAdmin :
163            raise PyKotaCommandLineError, "%s : %s" % (pwd.getpwuid(os.geteuid())[0], _("You're not allowed to use this command."))
164           
165        suffix = (options["groups"] and "Group") or "User"       
166       
167        if options["delete"] :   
168            self.display("%s...\n" % _("Deletion"))
169            todelete = getattr(self.storage, "getMatching%ss" % suffix)(",".join(names))
170            nbtotal = len(todelete)
171            for i in range(nbtotal) :
172                todelete[i].delete()
173                percent = 100.0 * float(i) / float(nbtotal)
174                self.display("\r%.02f%%" % percent)
175        else :
176            if options["add"] :   
177                self.display("%s...\n" % _("Creation"))
178                rejectunknown = self.config.getRejectUnknown()   
179                entries = []
180                nbtotal = len(names)
181                for i in range(nbtotal) :
182                    ename = names[i]
183                    email = ""
184                    if not options["groups"] :
185                        splitname = ename.split('/', 1)     # username/email
186                        if len(splitname) == 1 :
187                            splitname.append("")
188                        (ename, email) = splitname
189                        if email and (email.count('@') != 1) :
190                            self.printInfo(_("Invalid email address %s") % email)
191                            email = ""
192                    entry = getattr(self.storage, "get%s" % suffix)(ename)
193                    if entry.Exists :
194                        if options["skipexisting"] :
195                            self.printInfo(_("%s %s already exists, skipping.") % (suffix, entry.Name))
196                        else :   
197                            self.printInfo(_("%s %s already exists, will be modified.") % (suffix, entry.Name))
198                            entries.append(entry)
199                    else :
200                        if self.isValidName(entry.Name) :
201                            reject = 0
202                            if rejectunknown :
203                                if options["groups"] :
204                                    try :
205                                        grp.getgrnam(entry.Name)
206                                    except KeyError :   
207                                        self.printInfo(_("Unknown group %s") % entry.Name, "error")
208                                        reject = 1
209                                else :   
210                                    try :
211                                        pwd.getpwnam(entry.Name)
212                                    except KeyError :   
213                                        self.printInfo(_("Unknown user %s") % entry.Name, "error")
214                                        reject = 1
215                            if not reject :       
216                                if email :
217                                    entry.Email = email
218                                entry = getattr(self.storage, "add%s" % suffix)(entry)
219                                entries.append(entry)
220                        else :   
221                            if options["groups"] :
222                                self.printInfo(_("Invalid group name %s") % entry.Name)
223                            else :   
224                                self.printInfo(_("Invalid user name %s") % entry.Name)
225                               
226                    percent = 100.0 * float(i) / float(nbtotal)
227                    self.display("\r%.02f%%" % percent)
228                self.display("\r100.00%%\r        \r%s\n" % _("Done."))
229            else :       
230                if not names :
231                    names = ["*"]
232                entries = getattr(self.storage, "getMatching%ss" % suffix)(",".join(names))
233                if not entries :
234                    raise PyKotaCommandLineError, _("There's no %s matching %s") % (suffix.lower(), " ".join(names))
235                       
236            if options["list"] :
237                if suffix == "User" :
238                    maildomain = self.config.getMailDomain()
239                    smtpserver = self.config.getSMTPServer()
240                    for entry in entries :
241                        email = entry.Email
242                        if not email :
243                            if maildomain :     
244                                email = "%s@%s" % (entry.Name, maildomain)
245                            elif smtpserver :   
246                                email = "%s@%s" % (entry.Name, smtpserver)
247                            else :   
248                                email = "%s@%s" % (entry.Name, "localhost")
249                        msg = "%s - <%s>" % (entry.Name, email)
250                        if entry.Description :
251                            msg += " - %s" % entry.Description
252                        print msg   
253                        print "    %s" % (_("Limited by : %s") % entry.LimitBy)
254                        print "    %s" % (_("Account balance : %.2f") % (entry.AccountBalance or 0.0))
255                        print "    %s" % (_("Total paid so far : %.2f") % (entry.LifeTimePaid or 0.0))
256                        print "    %s" % (_("Overcharging factor : %.2f") % entry.OverCharge)
257                else :   
258                    for entry in entries :
259                        msg = "%s" % entry.Name
260                        if entry.Description :
261                            msg += " - %s" % entry.Description
262                        print msg   
263                        print "    %s" % (_("Limited by : %s") % entry.LimitBy)
264                        print "    %s" % (_("Group balance : %.2f") % (entry.AccountBalance or 0.0))
265                        print "    %s" % (_("Total paid so far : %.2f") % (entry.LifeTimePaid or 0.0))
266            else :
267                self.display("%s...\n" % _("Modification"))
268               
269                limitby = options["limitby"]
270                if limitby :
271                    limitby = limitby.strip().lower()
272                if limitby :
273                    if limitby not in ('quota', 'balance', 'noquota', \
274                                                'noprint', 'nochange') :
275                        raise PyKotaCommandLineError, _("Invalid limitby value %s") % options["limitby"]
276                    if (limitby in ('nochange', 'noprint')) and options["groups"] :   
277                        raise PyKotaCommandLineError, _("Invalid limitby value %s") % options["limitby"]
278                   
279                overcharge = options["overcharge"]
280                if overcharge :
281                    try :
282                        overcharge = float(overcharge.strip())
283                    except (ValueError, AttributeError) :   
284                        raise PyKotaCommandLineError, _("Invalid overcharge value %s") % options["overcharge"]
285                       
286                balance = options["balance"]
287                if balance :
288                    balance = balance.strip()
289                    try :
290                        balancevalue = float(balance)
291                    except ValueError :   
292                        raise PyKotaCommandLineError, _("Invalid balance value %s") % options["balance"]
293                   
294                if options["ingroups"] :
295                    usersgroups = self.storage.getMatchingGroups(options["ingroups"])
296                    if not usersgroups :
297                        raise PyKotaCommandLineError, _("There's no users group matching %s") % " ".join(options["ingroups"].split(','))
298                else :         
299                    usersgroups = []
300                       
301                description = options["description"]
302                if description :
303                    description = options["description"].strip()
304                   
305                comment = options["comment"]
306                if comment :
307                    comment = options["comment"].strip()
308                   
309                nbtotal = len(entries)
310                for i in range(nbtotal) :       
311                    entry = entries[i]
312                   
313                    # We need a transaction because we may have to create a record
314                    # in the payments' table at the same time we modify the user's balance.
315                    self.storage.beginTransaction()
316                    try :
317                        if description is not None : # NB : "" is allowed !
318                            entry.setDescription(description)
319                        if limitby :   
320                            entry.setLimitBy(limitby)
321                           
322                        if suffix == "User" :   
323                            if overcharge is not None : # NB : 0 is allowed !     
324                                entry.setOverChargeFactor(overcharge)
325                       
326                        if suffix == "User" :
327                            if balance :
328                                if balance.startswith("+") or balance.startswith("-") :
329                                    newbalance = float(entry.AccountBalance or 0.0) + balancevalue
330                                    newlifetimepaid = float(entry.LifeTimePaid or 0.0) + balancevalue
331                                    entry.setAccountBalance(newbalance, newlifetimepaid, comment)
332                                else :
333                                    diff = balancevalue - float(entry.AccountBalance or 0.0)
334                                    newlifetimepaid = float(entry.LifeTimePaid or 0.0) + diff
335                                    entry.setAccountBalance(balancevalue, newlifetimepaid, comment)
336                                           
337                            for ugroup in usersgroups :
338                                if options["remove"] :
339                                    ugroup.delUserFromGroup(entry)
340                                else :
341                                    ugroup.addUserToGroup(entry)
342                        entry.save()   
343                    except :
344                        self.storage.rollbackTransaction()
345                        raise
346                    else :   
347                        self.storage.commitTransaction()
348
349                    percent = 100.0 * float(i) / float(nbtotal)
350                    self.display("\r%.02f%%" % percent)
351                               
352        if not options["list"] :               
353            self.display("\r100.00%%\r        \r%s\n" % _("Done."))
354                     
355if __name__ == "__main__" : 
356    retcode = 0
357    try :
358        defaults = { \
359                     "comment" : "", \
360                   }
361        short_options = "hvaD:dgl:rso:i:b:C:L"
362        long_options = ["help", "version", "add", "description=", \
363                        "delete", "groups", "list", "remove", \
364                        "skipexisting", "overcharge=", \
365                        "ingroups=", "limitby=", "balance=", "comment=", \
366                       ]
367                       
368       
369        # Initializes the command line tool
370        manager = PKUsers(doc=__doc__)
371        manager.deferredInit()
372       
373        # parse and checks the command line
374        (options, args) = manager.parseCommandline(sys.argv[1:], short_options, long_options)
375       
376        # sets long options
377        options["help"] = options["h"] or options["help"]
378        options["version"] = options["v"] or options["version"]
379        options["add"] = options["a"] or options["add"]
380        options["description"] = options["D"] or options["description"]
381        options["delete"] = options["d"] or options["delete"] 
382        options["groups"] = options["g"] or options["groups"]
383        options["list"] = options["L"] or options["list"]
384        options["remove"] = options["r"] or options["remove"]
385        options["skipexisting"] = options["s"] or options["skipexisting"]
386        options["limitby"] = options["l"] or options["limitby"]
387        options["balance"] = options["b"] or options["balance"] 
388        options["ingroups"] = options["i"] or options["ingroups"]
389        options["overcharge"] = options["o"] or options["overcharge"]
390        options["comment"] = options["C"] or options["comment"] or defaults["comment"]
391       
392        if options["help"] :
393            manager.display_usage_and_quit()
394        elif options["version"] :
395            manager.display_version_and_quit()
396        elif (options["delete"] and (options["add"] or options["remove"] or options["description"])) \
397           or (options["skipexisting"] and not options["add"]) \
398           or (options["list"] and (options["add"] or options["delete"] or options["remove"] or options["description"])) \
399           or (options["groups"] and (options["balance"] or options["ingroups"] or options["overcharge"])) :
400            raise PyKotaCommandLineError, _("incompatible options, see help.")
401        elif options["remove"] and not options["ingroups"] :   
402            raise PyKotaCommandLineError, _("You have to pass user groups names on the command line")
403        elif (not args) and options["add"] :
404            raise PyKotaCommandLineError, _("You have to pass user or group names on the command line")
405        else :
406            retcode = manager.main(args, options)
407    except KeyboardInterrupt :       
408        sys.stderr.write("\nInterrupted with Ctrl+C !\n")
409        retcode = -3
410    except PyKotaCommandLineError, msg :   
411        sys.stderr.write("%s : %s\n" % (sys.argv[0], msg))
412        retcode = -2
413    except SystemExit :       
414        pass
415    except :
416        try :
417            manager.crashed("pkusers failed")
418        except :   
419            crashed("pkusers failed")
420        retcode = -1
421
422    try :
423        manager.storage.close()
424    except (TypeError, NameError, AttributeError) :   
425        pass
426       
427    sys.exit(retcode)   
Note: See TracBrowser for help on using the browser.