root / pykota / trunk / bin / pkusers @ 3457

Revision 3434, 20.7 kB (checked in by jerome, 16 years ago)

Moved the progress report code to its own module.

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