root / pykota / trunk / bin / pkusers @ 3432

Revision 3432, 20.6 kB (checked in by jerome, 16 years ago)

edpykota now supports new style command line handling.

  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
Line 
1#! /usr/bin/env python
2# -*- coding: utf-8 -*-*-
3#
4# PyKota : Print Quotas for CUPS
5#
6# (c) 2003, 2004, 2005, 2006, 2007, 2008 Jerome Alet <alet@librelogiciel.com>
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation, either version 3 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program.  If not, see <http://www.gnu.org/licenses/>.
19#
20# $Id$
21#
22#
23
24"""A users and groups manager for PyKota."""
25
26import sys
27import os
28import pwd
29import grp
30
31import pykota.appinit
32from pykota.utils import run
33from pykota.commandline import PyKotaOptionParser
34from pykota.errors import PyKotaCommandLineError
35from pykota.tool import Percent, PyKotaTool
36from pykota.storage import StorageUser, StorageGroup
37
38class PKUsers(PyKotaTool) :
39    """A class for a users and users groups manager."""
40    def modifyEntry(self, entry, groups, limitby, description, overcharge=None, balance=None, balancevalue=None, comment=None, email=None) :
41        """Modifies an entry."""
42        if description is not None : # NB : "" is allowed !
43            entry.setDescription(description)
44        if limitby :
45            entry.setLimitBy(limitby)
46        if not groups :
47            if email is not None :      # we allow "" to empty the field
48                if email.startswith("@") :
49                    email = "%s%s" % (entry.Name, email)
50                if email and email.count('@') != 1 :
51                    raise PyKotaCommandLineError, _("Invalid email address %s") % email
52                entry.setEmail(email)
53            if overcharge is not None : # NB : 0 is allowed !
54                entry.setOverChargeFactor(overcharge)
55            if balance :
56                if balance.startswith("+") or balance.startswith("-") :
57                    newbalance = float(entry.AccountBalance or 0.0) + balancevalue
58                    newlifetimepaid = float(entry.LifeTimePaid or 0.0) + balancevalue
59                    entry.setAccountBalance(newbalance, newlifetimepaid, comment)
60                else :
61                    diff = balancevalue - float(entry.AccountBalance or 0.0)
62                    newlifetimepaid = float(entry.LifeTimePaid or 0.0) + diff
63                    entry.setAccountBalance(balancevalue, newlifetimepaid, comment)
64
65    def manageUsersGroups(self, ugroups, user, remove) :
66        """Manage user group membership."""
67        for ugroup in ugroups :
68            if remove :
69                ugroup.delUserFromGroup(user)
70            else :
71                ugroup.addUserToGroup(user)
72
73    def main(self, names, options) :
74        """Manage users or groups."""
75        islist = (options.action == "list")
76        isadd = (options.action == "add")
77        isdelete = (options.action == "delete")
78
79        if not islist :
80            self.adminOnly()
81
82        names = self.sanitizeNames(names, options.groups)
83        if not names :
84            if isdelete or isadd :
85                raise PyKotaCommandLineError, _("You must specify users or groups names on the command line.")
86            names = [u"*"]
87
88        if options.remove and not options.ingroups :
89            raise PyKotaCommandLineError, _("You must specify users groups names on the command line.")
90        elif (((islist or isdelete) and (options.limitby  \
91                                      or options.balance \
92                                      or options.email \
93                                      or options.remove \
94                                      or options.overcharge \
95                                      or options.ingroups \
96                                      or options.description \
97                                      or options.skipexisting \
98                                      or options.comment))) \
99             or (options.groups and (options.ingroups \
100                                  or options.balance \
101                                  or options.email \
102                                  or options.remove \
103                                  or options.overcharge \
104                                  or options.comment)) :
105            raise PyKotaCommandLineError, _("Incompatible command line options. Please look at the online help or manual page.")
106
107        suffix = (options.groups and "Group") or "User"
108
109        if not islist :
110            percent = Percent(self)
111
112        if not isadd :
113            if not islist :
114                percent.display("%s..." % _("Extracting datas"))
115            entries = getattr(self.storage, "getMatching%ss" % suffix)(",".join(names))
116            if not entries :
117                if not islist :
118                    percent.display("\n")
119                raise PyKotaCommandLineError, _("There's no %s matching %s") \
120                    % (_(suffix.lower()), " ".join(names))
121            if not islist :
122                percent.setSize(len(entries))
123
124        if islist :
125            if suffix == "User" :
126                maildomain = self.config.getMailDomain()
127                smtpserver = self.config.getSMTPServer()
128                for entry in entries :
129                    email = entry.Email
130                    if not email :
131                        if maildomain :
132                            email = "%s@%s" % (entry.Name, maildomain)
133                        elif smtpserver :
134                            email = "%s@%s" % (entry.Name, smtpserver)
135                        else :
136                            email = "%s@%s" % (entry.Name, "localhost")
137                    msg = "%s - <%s>" % (entry.Name, email)
138                    if entry.Description :
139                        msg += " - %s" % entry.Description
140                    self.display("%s\n" % msg)
141                    self.display("    %s\n" % (_("Limited by : %s") % entry.LimitBy))
142                    self.display("    %s\n" % (_("Account balance : %.2f") % (entry.AccountBalance or 0)))
143                    self.display("    %s\n" % (_("Total paid so far : %.2f") % (entry.LifeTimePaid or 0)))
144                    self.display("    %s\n" % (_("Overcharging factor : %.2f") % entry.OverCharge))
145                    self.display("\n")
146            else :
147                for entry in entries :
148                    msg = "%s" % entry.Name
149                    if entry.Description :
150                        msg += " - %s" % entry.Description
151                    self.display("%s\n" % msg)
152                    self.display("    %s\n" % (_("Limited by : %s") % entry.LimitBy))
153                    self.display("    %s\n" % (_("Group balance : %.2f") % (entry.AccountBalance or 0)))
154                    self.display("    %s\n" % (_("Total paid so far : %.2f") % (entry.LifeTimePaid or 0)))
155                    self.display("\n")
156        elif isdelete :
157            percent.display("\n%s..." % _("Deletion"))
158            getattr(self.storage, "deleteMany%ss" % suffix)(entries)
159            percent.display("\n")
160        else :
161            limitby = options.limitby
162            if limitby :
163                limitby = limitby.strip().lower()
164            if limitby :
165                if limitby not in ('quota',
166                                   'balance',
167                                   'noquota',
168                                   'noprint',
169                                   'nochange') :
170                    raise PyKotaCommandLineError, _("Invalid limitby value %s") \
171                        % options.limitby
172                if (limitby in ('nochange', 'noprint')) and options.groups :
173                    raise PyKotaCommandLineError, _("Invalid limitby value %s") \
174                        % options.limitby
175
176            balance = options.balance
177            if balance :
178                balance = balance.strip()
179                try :
180                    balancevalue = float(balance)
181                except ValueError :
182                    raise PyKotaCommandLineError, _("Invalid balance value %s") \
183                        % options.balance
184            else :
185                balancevalue = None
186
187            if options.ingroups :
188                usersgroups = self.storage.getMatchingGroups(options.ingroups)
189                if not usersgroups :
190                    raise PyKotaCommandLineError, _("There's no users group matching %s") \
191                        % " ".join(options.ingroups.split(','))
192            else :
193                usersgroups = []
194
195            if options.description :
196                options.description = options.description.strip()
197
198            if options.comment :
199                options.comment = options.comment.strip()
200
201            if options.email :
202                options.email = options.email.strip()
203
204            self.storage.beginTransaction()
205            try :
206                if isadd :
207                    rejectunknown = self.config.getRejectUnknown()
208                    percent.display("%s...\n" % _("Creation"))
209                    percent.setSize(len(names))
210                    for ename in names :
211                        useremail = None
212                        if not options.groups :
213                            splitname = ename.split('/', 1)     # username/email
214                            if len(splitname) == 1 :
215                                splitname.append("")
216                            (ename, useremail) = splitname
217                        if self.isValidName(ename) :
218                            reject = 0
219                            if rejectunknown :
220                                if options.groups :
221                                    try :
222                                        grp.getgrnam(ename)
223                                    except KeyError :
224                                        self.printInfo(_("Unknown group %s") % ename, "error")
225                                        reject = 1
226                                else :
227                                    try :
228                                        pwd.getpwnam(ename)
229                                    except KeyError :
230                                        self.printInfo(_("Unknown user %s") % ename, "error")
231                                        reject = 1
232                            if not reject :
233                                entry = globals()["Storage%s" % suffix](self.storage, ename)
234                                if options.groups :
235                                    self.modifyEntry(entry,
236                                                     options.groups,
237                                                     limitby,
238                                                     options.description)
239                                else :
240                                    self.modifyEntry(entry,
241                                                     options.groups,
242                                                     limitby,
243                                                     options.description,
244                                                     options.overcharge,
245                                                     balance,
246                                                     balancevalue,
247                                                     options.comment,
248                                                     useremail or options.email)
249                                oldentry = getattr(self.storage, "add%s" % suffix)(entry)
250                                if oldentry is not None :
251                                    if options.skipexisting :
252                                        self.logdebug(_("%s %s already exists, skipping.") \
253                                                          % (_(suffix), ename))
254                                    else :
255                                        self.logdebug(_("%s %s already exists, will be modified.") \
256                                                          % (_(suffix), ename))
257                                        if options.groups :
258                                            self.modifyEntry(oldentry,
259                                                             options.groups,
260                                                             limitby,
261                                                             options.description)
262                                        else :
263                                            self.modifyEntry(oldentry,
264                                                             options.groups,
265                                                             limitby,
266                                                             options.description,
267                                                             options.overcharge,
268                                                             balance,
269                                                             balancevalue,
270                                                             options.comment,
271                                                             useremail or options.email)
272                                        oldentry.save()
273                                        if not options.groups :
274                                            self.manageUsersGroups(usersgroups,
275                                                                   oldentry,
276                                                                   options.remove)
277                                elif usersgroups and not options.groups :
278                                    self.manageUsersGroups(usersgroups, \
279                                                           self.storage.getUser(ename), \
280                                                           options.remove)
281                        else :
282                            raise PyKotaCommandLineError, _("Invalid name %s") % ename
283                        percent.oneMore()
284                else :
285                    percent.display("\n%s...\n" % _("Modification"))
286                    for entry in entries :
287                        if options.groups :
288                            self.modifyEntry(entry,
289                                             options.groups,
290                                             limitby,
291                                             options.description)
292                        else :
293                            self.modifyEntry(entry,
294                                             options.groups,
295                                             limitby,
296                                             options.description,
297                                             options.overcharge,
298                                             balance,
299                                             balancevalue,
300                                             options.comment,
301                                             options.email)
302                            self.manageUsersGroups(usersgroups,
303                                                   entry,
304                                                   options.remove)
305                        entry.save()
306                        percent.oneMore()
307            except :
308                self.storage.rollbackTransaction()
309                raise
310            else :
311                self.storage.commitTransaction()
312
313        if not islist :
314            percent.done()
315
316if __name__ == "__main__" :
317    parser = PyKotaOptionParser(description=_("Manages PyKota users or users groups."),
318                                usage="pkusers [options] [usernames|groupnames]")
319    parser.add_option("-a", "--add",
320                            action="store_const",
321                            const="add",
322                            dest="action",
323                            help=_("Add new, or modify existing, users or groups."))
324    parser.add_option("-b", "--balance",
325                            dest="balance",
326                            help=_("Set an user's account balance. The value can also be increased or decreased when the value is prefixed with '+' or '-'. Users groups don't have a real account balance, instead the sum of their members' account balances is used."))
327    parser.add_option("-C", "--comment",
328                            dest="comment",
329                            default="",
330                            help=_("Associate a textual comment with a change in an user's account balance. Only meaningful when --balance is also used."))
331    parser.add_option("-d", "--delete",
332                            action="store_const",
333                            const="delete",
334                            dest="action",
335                            help=_("Delete the specified users or groups. Also purge the print quota entries and printing history matching the specified users or groups."))
336    parser.add_option("-D", "--description",
337                            dest="description",
338                            help=_("Set a textual description for the specified users or groups."))
339    parser.add_option("-e", "--email",
340                            dest="email",
341                            help=_("Set an user's email address. If this parameter begins with '@' then the username is prepended to this parameter to form a valid email address."))
342    parser.add_option("-g", "--groups",
343                            action="store_true",
344                            dest="groups",
345                            help=_("Manage users groups instead of users."))
346    parser.add_option("-i", "--ingroups",
347                            dest="ingroups",
348                            help=_("Put the specified users into the specified groups. When combined with the --remove option, users are removed from the specified groups instead."))
349    parser.add_option("-l", "--limitby",
350                            dest="limitby",
351                            help=_("Set the limiting factor for the specified users or groups. Can be any of 'quota' (limit by number of pages per printer), 'balance' (limit by number of credits), 'noquota' (no limit but accounting done), 'nochange' (no limit and not accounting), or 'noprint' (printing is denied). The two latter ones are not supported for groups."))
352    parser.add_option("-L", "--list",
353                            action="store_const",
354                            const="list",
355                            dest="action",
356                            help=_("Display detailed informations about the specified users or groups."))
357    parser.add_option("-o", "--overcharge",
358                            type="float",
359                            dest="overcharge",
360                            help=_("Set the overcharging factor applied to the specified users when computing the cost of a print job. Any floating point value can be used, allowing you to express your creativity..."))
361    parser.add_option("-r", "--remove",
362                            action="store_true",
363                            dest="remove",
364                            help=_("When combined with the --ingroups option, remove users from the specified users groups."))
365    parser.add_option("-s", "--skipexisting",
366                            action="store_true",
367                            dest="skipexisting",
368                            help=_("If --add is used, ensure that existing users or groups won't be modified."))
369
370    parser.add_example("--add john paul george ringo/ringo@example.com",
371                       _("Would make users 'john', 'paul', 'george' and 'ringo' be known to PyKota. User 'ringo''s email address would be set to 'ringo@example.com'."))
372    parser.add_example("--add --groups coders it",
373                       _("Would create two users groups named 'coders' and 'it'."))
374    parser.add_example("--add --ingroups coders,it jerome",
375                       _("Would add user 'jerome' and put him into the 'coders' and 'it' groups. Both groups would have to be existing."))
376    parser.add_example("--limitby balance --balance 10.0 john",
377                       _("Would give 10.0 credits to 'john' and make his printing be limited by his account balance."))
378    parser.add_example('--balance +10.0 --comment "He paid with his blood." jerome',
379                       _("Would add 10.0 credits to 'jerome''s account and register a comment associated with his payment."))
380    parser.add_example('--delete "jer*" "rach*"',
381                       _("Would delete all user accounts whose names begin with either 'jer' or 'rach'."))
382    parser.add_example("--overcharge -1.50 theboss",
383                       _("Would make the boss earn money whenever he prints."))
384    parser.add_example("--email @example.com",
385                       _("Would set the email address for each existing user to username@example.com"))
386    parser.add_example("--list",
387                       _("Would list all users."))
388    run(parser, PKUsers)
Note: See TracBrowser for help on using the browser.