root / pykota / trunk / bin / pkusers @ 2990

Revision 2937, 21.4 kB (checked in by jerome, 18 years ago)

pkusers now supports the --email command line option.

  • 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                raise PyKotaCommandLineError, _("There's no %s matching %s") % (_(suffix.lower()), " ".join(names))
218            if not options["list"] :   
219                percent.setSize(len(entries))
220               
221        if options["list"] :
222            if suffix == "User" :
223                maildomain = self.config.getMailDomain()
224                smtpserver = self.config.getSMTPServer()
225                for entry in entries :
226                    email = entry.Email
227                    if not email :
228                        if maildomain :     
229                            email = "%s@%s" % (entry.Name, maildomain)
230                        elif smtpserver :   
231                            email = "%s@%s" % (entry.Name, smtpserver)
232                        else :   
233                            email = "%s@%s" % (entry.Name, "localhost")
234                    msg = "%s - <%s>" % (entry.Name, email)
235                    if entry.Description :
236                        msg += " - %s" % entry.Description
237                    print msg   
238                    print "    %s" % (_("Limited by : %s") % entry.LimitBy)
239                    print "    %s" % (_("Account balance : %.2f") % (entry.AccountBalance or 0.0))
240                    print "    %s" % (_("Total paid so far : %.2f") % (entry.LifeTimePaid or 0.0))
241                    print "    %s" % (_("Overcharging factor : %.2f") % entry.OverCharge)
242                    print
243            else :   
244                for entry in entries :
245                    msg = "%s" % entry.Name
246                    if entry.Description :
247                        msg += " - %s" % entry.Description
248                    print msg   
249                    print "    %s" % (_("Limited by : %s") % entry.LimitBy)
250                    print "    %s" % (_("Group balance : %.2f") % (entry.AccountBalance or 0.0))
251                    print "    %s" % (_("Total paid so far : %.2f") % (entry.LifeTimePaid or 0.0))
252                    print
253        elif options["delete"] :   
254            percent.display("\n%s..." % _("Deletion"))
255            getattr(self.storage, "deleteMany%ss" % suffix)(entries)
256            percent.display("\n")
257        else :
258            limitby = options["limitby"]
259            if limitby :
260                limitby = limitby.strip().lower()
261            if limitby :
262                if limitby not in ('quota', 'balance', 'noquota', \
263                                            'noprint', 'nochange') :
264                    raise PyKotaCommandLineError, _("Invalid limitby value %s") % options["limitby"]
265                if (limitby in ('nochange', 'noprint')) and options["groups"] :   
266                    raise PyKotaCommandLineError, _("Invalid limitby value %s") % options["limitby"]
267               
268            overcharge = options["overcharge"]
269            if overcharge :
270                try :
271                    overcharge = float(overcharge.strip())
272                except (ValueError, AttributeError) :   
273                    raise PyKotaCommandLineError, _("Invalid overcharge value %s") % options["overcharge"]
274                   
275            balance = options["balance"]
276            if balance :
277                balance = balance.strip()
278                try :
279                    balancevalue = float(balance)
280                except ValueError :   
281                    raise PyKotaCommandLineError, _("Invalid balance value %s") % options["balance"]
282            else :   
283                balancevalue = None
284               
285            if options["ingroups"] :
286                usersgroups = self.storage.getMatchingGroups(options["ingroups"])
287                if not usersgroups :
288                    raise PyKotaCommandLineError, _("There's no users group matching %s") % " ".join(options["ingroups"].split(','))
289            else :         
290                usersgroups = []
291                   
292            description = options["description"]
293            if description :
294                description = description.strip()
295               
296            comment = options["comment"]
297            if comment :
298                comment = comment.strip()
299            email = options["email"]   
300            if email :
301                email = email.strip()
302            skipexisting = options["skipexisting"]   
303            groups = options["groups"]
304            remove = options["remove"]
305            self.storage.beginTransaction()
306            try :   
307                if options["add"] :   
308                    rejectunknown = self.config.getRejectUnknown()   
309                    percent.display("%s...\n" % _("Creation"))
310                    percent.setSize(len(names))
311                    for ename in names :
312                        useremail = None
313                        if not groups :
314                            splitname = ename.split('/', 1)     # username/email
315                            if len(splitname) == 1 :
316                                splitname.append("")
317                            (ename, useremail) = splitname
318                        if self.isValidName(ename) :
319                            reject = 0
320                            if rejectunknown :
321                                if groups :
322                                    try :
323                                        grp.getgrnam(ename)
324                                    except KeyError :   
325                                        self.printInfo(_("Unknown group %s") % ename, "error")
326                                        reject = 1
327                                else :   
328                                    try :
329                                        pwd.getpwnam(ename)
330                                    except KeyError :   
331                                        self.printInfo(_("Unknown user %s") % ename, "error")
332                                        reject = 1
333                            if not reject :       
334                                entry = globals()["Storage%s" % suffix](self.storage, ename)
335                                if groups :
336                                    self.modifyEntry(entry, groups, limitby, \
337                                                     description)
338                                else :   
339                                    self.modifyEntry(entry, groups, limitby, \
340                                                     description, overcharge,\
341                                                     balance, balancevalue, \
342                                                     comment, useremail or email)
343                                oldentry = getattr(self.storage, "add%s" % suffix)(entry)
344                                if oldentry is not None :
345                                    if skipexisting :
346                                        self.logdebug(_("%s %s already exists, skipping.") % (_(suffix), ename))
347                                    else :   
348                                        self.logdebug(_("%s %s already exists, will be modified.") % (_(suffix), ename))
349                                        if groups :
350                                            self.modifyEntry(oldentry, groups, \
351                                                     limitby, description)
352                                        else :
353                                            self.modifyEntry(oldentry, groups, limitby, \
354                                                     description, overcharge,\
355                                                     balance, balancevalue, \
356                                                     comment, useremail or email)
357                                        oldentry.save()
358                                        if not groups :
359                                            self.manageUsersGroups(usersgroups, oldentry, remove)
360                                elif usersgroups and not groups :
361                                    self.manageUsersGroups(usersgroups, \
362                                                           self.storage.getUser(ename), \
363                                                           remove)
364                        else :
365                            raise PyKotaCommandLineError, _("Invalid name %s") % ename
366                        percent.oneMore()
367                else :
368                    percent.display("\n%s...\n" % _("Modification"))
369                    for entry in entries :
370                        if groups :
371                            self.modifyEntry(entry, groups, limitby, description)
372                        else :   
373                            self.modifyEntry(entry, groups, limitby, description, \
374                                             overcharge, balance, balancevalue, \
375                                             comment, email)
376                            self.manageUsersGroups(usersgroups, entry, remove)               
377                        entry.save()   
378                        percent.oneMore()
379            except :                   
380                self.storage.rollbackTransaction()
381                raise
382            else :   
383                self.storage.commitTransaction()
384               
385        if not options["list"] :
386            percent.done()
387                     
388if __name__ == "__main__" : 
389    retcode = 0
390    try :
391        defaults = { \
392                     "comment" : "", \
393                   }
394        short_options = "hvaD:dgl:rso:i:b:C:Le:"
395        long_options = ["help", "version", "add", "description=", \
396                        "delete", "groups", "list", "remove", \
397                        "skipexisting", "overcharge=", "email=", \
398                        "ingroups=", "limitby=", "balance=", "comment=", \
399                       ]
400                       
401       
402        # Initializes the command line tool
403        manager = PKUsers(doc=__doc__)
404        manager.deferredInit()
405       
406        # parse and checks the command line
407        (options, args) = manager.parseCommandline(sys.argv[1:], short_options, long_options)
408       
409        # sets long options
410        options["help"] = options["h"] or options["help"]
411        options["version"] = options["v"] or options["version"]
412        options["add"] = options["a"] or options["add"]
413        options["description"] = options["D"] or options["description"]
414        options["delete"] = options["d"] or options["delete"] 
415        options["groups"] = options["g"] or options["groups"]
416        options["list"] = options["L"] or options["list"]
417        options["remove"] = options["r"] or options["remove"]
418        options["skipexisting"] = options["s"] or options["skipexisting"]
419        options["limitby"] = options["l"] or options["limitby"]
420        options["balance"] = options["b"] or options["balance"] 
421        options["ingroups"] = options["i"] or options["ingroups"]
422        options["overcharge"] = options["o"] or options["overcharge"]
423        options["comment"] = options["C"] or options["comment"] or defaults["comment"]
424        options["email"] = options["e"] or options["email"]
425       
426        if options["help"] :
427            manager.display_usage_and_quit()
428        elif options["version"] :
429            manager.display_version_and_quit()
430        elif (options["delete"] and (options["add"] or options["remove"] or options["description"] or options["email"])) \
431           or (options["skipexisting"] and not options["add"]) \
432           or (options["list"] and (options["add"] or options["delete"] or options["remove"] or options["description"] or options["email"])) \
433           or (options["groups"] and (options["balance"] or options["ingroups"] or options["overcharge"])) :
434            raise PyKotaCommandLineError, _("incompatible options, see help.")
435        elif options["remove"] and not options["ingroups"] :   
436            raise PyKotaCommandLineError, _("You have to pass user groups names on the command line")
437        elif (not args) and (options["add"] or options["delete"]) :
438            raise PyKotaCommandLineError, _("You have to pass user or group names on the command line")
439        else :
440            retcode = manager.main(args, options)
441    except KeyboardInterrupt :       
442        sys.stderr.write("\nInterrupted with Ctrl+C !\n")
443        retcode = -3
444    except PyKotaCommandLineError, msg :   
445        sys.stderr.write("%s : %s\n" % (sys.argv[0], msg))
446        retcode = -2
447    except SystemExit :       
448        pass
449    except :
450        try :
451            manager.crashed("pkusers failed")
452        except :   
453            crashed("pkusers failed")
454        retcode = -1
455
456    try :
457        manager.storage.close()
458    except (TypeError, NameError, AttributeError) :   
459        pass
460       
461    sys.exit(retcode)   
Note: See TracBrowser for help on using the browser.