root / pykota / trunk / pykota / tool.py @ 1193

Revision 1193, 23.9 kB (checked in by jalet, 21 years ago)

Missing import statement.
Better documentation for mailto: external(...)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
RevLine 
[1144]1# PyKota
2# -*- coding: ISO-8859-15 -*-
[695]3
[952]4# PyKota - Print Quotas for CUPS and LPRng
[695]5#
6# (c) 2003 Jerome Alet <alet@librelogiciel.com>
[873]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 2 of the License, or
10# (at your option) any later version.
[695]11#
[873]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, write to the Free Software
19# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
[695]20#
21# $Id$
22#
23# $Log$
[1193]24# Revision 1.56  2003/11/19 07:40:20  jalet
25# Missing import statement.
26# Better documentation for mailto: external(...)
27#
[1192]28# Revision 1.55  2003/11/18 23:43:12  jalet
29# Mailto can be any external command now, as usual.
30#
[1171]31# Revision 1.54  2003/10/24 21:52:46  jalet
32# Now can force language when coming from CGI script.
33#
[1152]34# Revision 1.53  2003/10/08 21:41:38  jalet
35# External policies for printers works !
36# We can now auto-add users on first print, and do other useful things if needed.
37#
[1144]38# Revision 1.52  2003/10/07 09:07:28  jalet
39# Character encoding added to please latest version of Python
40#
[1140]41# Revision 1.51  2003/10/06 14:21:41  jalet
42# Test reversed to not retrieve group members when no messages for them.
43#
[1130]44# Revision 1.50  2003/10/02 20:23:18  jalet
45# Storage caching mechanism added.
46#
[1113]47# Revision 1.49  2003/07/29 20:55:17  jalet
48# 1.14 is out !
49#
[1091]50# Revision 1.48  2003/07/21 23:01:56  jalet
51# Modified some messages aout soft limit
52#
[1087]53# Revision 1.47  2003/07/16 21:53:08  jalet
54# Really big modifications wrt new configuration file's location and content.
55#
[1079]56# Revision 1.46  2003/07/09 20:17:07  jalet
57# Email field added to PostgreSQL schema
58#
[1077]59# Revision 1.45  2003/07/08 19:43:51  jalet
60# Configurable warning messages.
61# Poor man's treshold value added.
62#
[1068]63# Revision 1.44  2003/07/07 11:49:24  jalet
64# Lots of small fixes with the help of PyChecker
65#
[1061]66# Revision 1.43  2003/07/04 09:06:32  jalet
67# Small bug fix wrt undefined "LimitBy" field.
68#
[1048]69# Revision 1.42  2003/06/30 12:46:15  jalet
70# Extracted reporting code.
71#
[1041]72# Revision 1.41  2003/06/25 14:10:01  jalet
73# Hey, it may work (edpykota --reset excepted) !
74#
[1021]75# Revision 1.40  2003/06/10 16:37:54  jalet
76# Deletion of the second user which is not needed anymore.
77# Added a debug configuration field in /etc/pykota.conf
78# All queries can now be sent to the logger in debug mode, this will
79# greatly help improve performance when time for this will come.
80#
[973]81# Revision 1.39  2003/04/29 18:37:54  jalet
82# Pluggable accounting methods (actually doesn't support external scripts)
83#
[956]84# Revision 1.38  2003/04/24 11:53:48  jalet
85# Default policy for unknown users/groups is to DENY printing instead
86# of the previous default to ALLOW printing. This is to solve an accuracy
87# problem. If you set the policy to ALLOW, jobs printed by in nexistant user
88# (from PyKota's POV) will be charged to the next user who prints on the
89# same printer.
90#
[955]91# Revision 1.37  2003/04/24 08:08:27  jalet
92# Debug message forgotten
93#
[954]94# Revision 1.36  2003/04/24 07:59:40  jalet
95# LPRng support now works !
96#
[952]97# Revision 1.35  2003/04/23 22:13:57  jalet
98# Preliminary support for LPRng added BUT STILL UNTESTED.
99#
[929]100# Revision 1.34  2003/04/17 09:26:21  jalet
101# repykota now reports account balances too.
102#
[927]103# Revision 1.33  2003/04/16 12:35:49  jalet
104# Groups quota work now !
105#
[925]106# Revision 1.32  2003/04/16 08:53:14  jalet
107# Printing can now be limited either by user's account balance or by
108# page quota (the default). Quota report doesn't include account balance
109# yet, though.
110#
[915]111# Revision 1.31  2003/04/15 11:30:57  jalet
112# More work done on money print charging.
113# Minor bugs corrected.
114# All tools now access to the storage as priviledged users, repykota excepted.
115#
[900]116# Revision 1.30  2003/04/10 21:47:20  jalet
117# Job history added. Upgrade script neutralized for now !
118#
[873]119# Revision 1.29  2003/03/29 13:45:27  jalet
120# GPL paragraphs were incorrectly (from memory) copied into the sources.
121# Two README files were added.
122# Upgrade script for PostgreSQL pre 1.01 schema was added.
123#
[872]124# Revision 1.28  2003/03/29 13:08:28  jalet
125# Configuration is now expected to be found in /etc/pykota.conf instead of
126# in /etc/cups/pykota.conf
127# Installation script can move old config files to the new location if needed.
128# Better error handling if configuration file is absent.
129#
[852]130# Revision 1.27  2003/03/15 23:01:28  jalet
131# New mailto option in configuration file added.
132# No time to test this tonight (although it should work).
133#
[844]134# Revision 1.26  2003/03/09 23:58:16  jalet
135# Comment
136#
[834]137# Revision 1.25  2003/03/07 22:56:14  jalet
138# 0.99 is out with some bug fixes.
139#
[825]140# Revision 1.24  2003/02/27 23:48:41  jalet
141# Correctly maps PyKota's log levels to syslog log levels
142#
[824]143# Revision 1.23  2003/02/27 22:55:20  jalet
144# WARN log priority doesn't exist.
145#
[817]146# Revision 1.22  2003/02/27 09:09:20  jalet
147# Added a method to match strings against wildcard patterns
148#
[804]149# Revision 1.21  2003/02/17 23:01:56  jalet
150# Typos
151#
[802]152# Revision 1.20  2003/02/17 22:55:01  jalet
153# More options can now be set per printer or globally :
154#
[804]155#       admin
156#       adminmail
157#       gracedelay
158#       requester
[802]159#
160# the printer option has priority when both are defined.
161#
[788]162# Revision 1.19  2003/02/10 11:28:45  jalet
163# Localization
164#
[782]165# Revision 1.18  2003/02/10 01:02:17  jalet
166# External requester is about to work, but I must sleep
167#
[773]168# Revision 1.17  2003/02/09 13:05:43  jalet
169# Internationalization continues...
170#
[772]171# Revision 1.16  2003/02/09 12:56:53  jalet
172# Internationalization begins...
173#
[764]174# Revision 1.15  2003/02/08 22:09:52  jalet
175# Name check method moved here
176#
[742]177# Revision 1.14  2003/02/07 10:42:45  jalet
178# Indentation problem
179#
[733]180# Revision 1.13  2003/02/07 08:34:16  jalet
181# Test wrt date limit was wrong
182#
[729]183# Revision 1.12  2003/02/06 23:20:02  jalet
184# warnpykota doesn't need any user/group name argument, mimicing the
185# warnquota disk quota tool.
186#
[728]187# Revision 1.11  2003/02/06 22:54:33  jalet
188# warnpykota should be ok
189#
[722]190# Revision 1.10  2003/02/06 15:03:11  jalet
191# added a method to set the limit date
192#
[715]193# Revision 1.9  2003/02/06 10:39:23  jalet
194# Preliminary edpykota work.
195#
[713]196# Revision 1.8  2003/02/06 09:19:02  jalet
197# More robust behavior (hopefully) when the user or printer is not managed
198# correctly by the Quota System : e.g. cupsFilter added in ppd file, but
199# printer and/or user not 'yet?' in storage.
200#
[712]201# Revision 1.7  2003/02/06 00:00:45  jalet
202# Now includes the printer name in email messages
203#
[711]204# Revision 1.6  2003/02/05 23:55:02  jalet
205# Cleaner email messages
206#
[709]207# Revision 1.5  2003/02/05 23:45:09  jalet
208# Better DateTime manipulation wrt grace delay
209#
[708]210# Revision 1.4  2003/02/05 23:26:22  jalet
211# Incorrect handling of grace delay
212#
[699]213# Revision 1.3  2003/02/05 22:16:20  jalet
214# DEVICE_URI is undefined outside of CUPS, i.e. for normal command line tools
215#
[698]216# Revision 1.2  2003/02/05 22:10:29  jalet
217# Typos
218#
[695]219# Revision 1.1  2003/02/05 21:28:17  jalet
220# Initial import into CVS
221#
222#
223#
224
225import sys
[1193]226import os
[817]227import fnmatch
[715]228import getopt
[695]229import smtplib
[772]230import gettext
[782]231import locale
[695]232
[708]233from mx import DateTime
234
[715]235from pykota import version, config, storage, logger
[695]236
237class PyKotaToolError(Exception):
238    """An exception for PyKota config related stuff."""
239    def __init__(self, message = ""):
240        self.message = message
241        Exception.__init__(self, message)
242    def __repr__(self):
243        return self.message
244    __str__ = __repr__
245   
246class PyKotaTool :   
247    """Base class for all PyKota command line tools."""
[1171]248    def __init__(self, lang=None, doc="PyKota %s (c) 2003 %s" % (version.__version__, version.__author__)) :
[695]249        """Initializes the command line tool."""
[772]250        # locale stuff
[788]251        try :
[1171]252            locale.setlocale(locale.LC_ALL, lang)
[788]253            gettext.install("pykota")
254        except (locale.Error, IOError) :
255            gettext.NullTranslations().install()
[1171]256            sys.stderr.write("PyKota : Error while loading translations\n")
[772]257   
258        # pykota specific stuff
[715]259        self.documentation = doc
[1087]260        self.config = config.PyKotaConfig("/etc/pykota")
[1021]261        self.logger = logger.openLogger(self)
[1130]262        self.debug = self.config.getDebug()
[1021]263        self.storage = storage.openConnection(self)
[695]264        self.smtpserver = self.config.getSMTPServer()
265       
[1130]266    def logdebug(self, message) :   
267        """Logs something to debug output if debug is enabled."""
268        if self.debug :
269            self.logger.log_message(message, "debug")
270       
[1113]271    def clean(self) :   
272        """Ensures that the database is closed."""
273        try :
274            self.storage.close()
275        except (TypeError, NameError, AttributeError) :   
276            pass
277           
[715]278    def display_version_and_quit(self) :
279        """Displays version number, then exists successfully."""
[1113]280        self.clean()
[715]281        print version.__version__
282        sys.exit(0)
283   
284    def display_usage_and_quit(self) :
285        """Displays command line usage, then exists successfully."""
[1113]286        self.clean()
[715]287        print self.documentation
288        sys.exit(0)
289       
[729]290    def parseCommandline(self, argv, short, long, allownothing=0) :
[715]291        """Parses the command line, controlling options."""
292        # split options in two lists: those which need an argument, those which don't need any
293        withoutarg = []
294        witharg = []
295        lgs = len(short)
296        i = 0
297        while i < lgs :
298            ii = i + 1
299            if (ii < lgs) and (short[ii] == ':') :
300                # needs an argument
301                witharg.append(short[i])
302                ii = ii + 1 # skip the ':'
303            else :
304                # doesn't need an argument
305                withoutarg.append(short[i])
306            i = ii
307               
308        for option in long :
309            if option[-1] == '=' :
310                # needs an argument
311                witharg.append(option[:-1])
312            else :
313                # doesn't need an argument
314                withoutarg.append(option)
315       
316        # we begin with all possible options unset
317        parsed = {}
318        for option in withoutarg + witharg :
319            parsed[option] = None
320       
321        # then we parse the command line
322        args = []       # to not break if something unexpected happened
323        try :
324            options, args = getopt.getopt(argv, short, long)
325            if options :
326                for (o, v) in options :
327                    # we skip the '-' chars
328                    lgo = len(o)
329                    i = 0
330                    while (i < lgo) and (o[i] == '-') :
331                        i = i + 1
332                    o = o[i:]
333                    if o in witharg :
334                        # needs an argument : set it
335                        parsed[o] = v
336                    elif o in withoutarg :
337                        # doesn't need an argument : boolean
338                        parsed[o] = 1
339                    else :
340                        # should never occur
341                        raise PyKotaToolError, "Unexpected problem when parsing command line"
[729]342            elif (not args) and (not allownothing) and sys.stdin.isatty() : # no option and no argument, we display help if we are a tty
[715]343                self.display_usage_and_quit()
344        except getopt.error, msg :
345            sys.stderr.write("%s\n" % msg)
346            sys.stderr.flush()
347            self.display_usage_and_quit()
348        return (parsed, args)
349   
[764]350    def isValidName(self, name) :
351        """Checks if a user or printer name is valid."""
352        # unfortunately Python 2.1 string modules doesn't define ascii_letters...
353        asciiletters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
354        digits = '0123456789'
355        if name[0] in asciiletters :
356            validchars = asciiletters + digits + "-_"
357            for c in name[1:] :
358                if c not in validchars :
359                    return 0
360            return 1       
361        return 0
362       
[817]363    def matchString(self, s, patterns) :
364        """Returns 1 if the string s matches one of the patterns, else 0."""
365        for pattern in patterns :
366            if fnmatch.fnmatchcase(s, pattern) :
367                return 1
368        return 0
369       
[802]370    def sendMessage(self, adminmail, touser, fullmessage) :
[695]371        """Sends an email message containing headers to some user."""
372        if "@" not in touser :
373            touser = "%s@%s" % (touser, self.smtpserver)
374        server = smtplib.SMTP(self.smtpserver)
[1041]375        try :
376            server.sendmail(adminmail, [touser], fullmessage)
377        except smtplib.SMTPRecipientsRefused, answer :   
378            for (k, v) in answer.recipients.items() :
379                self.logger.log_message(_("Impossible to send mail to %s, error %s : %s") % (k, v[0], v[1]), "error")
[695]380        server.quit()
381       
[1079]382    def sendMessageToUser(self, admin, adminmail, user, subject, message) :
[695]383        """Sends an email message to a user."""
[802]384        message += _("\n\nPlease contact your system administrator :\n\n\t%s - <%s>\n") % (admin, adminmail)
[1079]385        self.sendMessage(adminmail, user.Email or user.Name, "Subject: %s\n\n%s" % (subject, message))
[695]386       
[802]387    def sendMessageToAdmin(self, adminmail, subject, message) :
[695]388        """Sends an email message to the Print Quota administrator."""
[802]389        self.sendMessage(adminmail, adminmail, "Subject: %s\n\n%s" % (subject, message))
[695]390       
[1041]391    def checkGroupPQuota(self, grouppquota) :   
[927]392        """Checks the group quota on a printer and deny or accept the job."""
[1041]393        group = grouppquota.Group
394        printer = grouppquota.Printer
[1061]395        if group.LimitBy and (group.LimitBy.lower() == "balance") : 
[1077]396            if group.AccountBalance <= 0.0 :
[1041]397                action = "DENY"
[1077]398            elif group.AccountBalance <= self.config.getPoorMan() :   
399                action = "WARN"
[927]400            else :   
[1041]401                action = "ALLOW"
[927]402        else :
[1041]403            if grouppquota.SoftLimit is not None :
404                softlimit = int(grouppquota.SoftLimit)
405                if grouppquota.PageCounter < softlimit :
406                    action = "ALLOW"
[927]407                else :   
[1041]408                    if grouppquota.HardLimit is None :
409                        # only a soft limit, this is equivalent to having only a hard limit
410                        action = "DENY"
[927]411                    else :   
[1041]412                        hardlimit = int(grouppquota.HardLimit)
413                        if softlimit <= grouppquota.PageCounter < hardlimit :   
414                            now = DateTime.now()
415                            if grouppquota.DateLimit is not None :
416                                datelimit = DateTime.ISO.ParseDateTime(grouppquota.DateLimit)
417                            else :
418                                datelimit = now + self.config.getGraceDelay(printer.Name)
419                                grouppquota.setDateLimit(datelimit)
420                            if now < datelimit :
421                                action = "WARN"
422                            else :   
[927]423                                action = "DENY"
[1041]424                        else :         
[927]425                            action = "DENY"
[1041]426            else :       
427                if grouppquota.HardLimit is not None :
428                    # no soft limit, only a hard one.
429                    hardlimit = int(grouppquota.HardLimit)
430                    if grouppquota.PageCounter < hardlimit :
[927]431                        action = "ALLOW"
[1041]432                    else :     
433                        action = "DENY"
434                else :
435                    # Both are unset, no quota, i.e. accounting only
436                    action = "ALLOW"
[927]437        return action
438   
[1041]439    def checkUserPQuota(self, userpquota) :
[708]440        """Checks the user quota on a printer and deny or accept the job."""
[1041]441        user = userpquota.User
442        printer = userpquota.Printer
443       
[927]444        # first we check any group the user is a member of
[1041]445        for group in self.storage.getUserGroups(user) :
446            grouppquota = self.storage.getGroupPQuota(group, printer)
447            if grouppquota.Exists :
448                action = self.checkGroupPQuota(grouppquota)
449                if action == "DENY" :
450                    return action
[927]451               
452        # then we check the user's own quota
[1152]453        # if we get there we are sure that policy is not EXTERNAL
454        (policy, dummy) = self.config.getPrinterPolicy(printer.Name)
[1061]455        if user.LimitBy and (user.LimitBy.lower() == "balance") : 
[1041]456            if user.AccountBalance is None :
[956]457                if policy == "ALLOW" :
[925]458                    action = "POLICY_ALLOW"
459                else :   
460                    action = "POLICY_DENY"
[1041]461                self.logger.log_message(_("Unable to find user %s's account balance, applying default policy (%s) for printer %s") % (user.Name, action, printer.Name))
[713]462            else :   
[1077]463                val = float(user.AccountBalance or 0.0)
464                if val <= 0.0 :
[925]465                    action = "DENY"
[1077]466                elif val <= self.config.getPoorMan() :   
467                    action = "WARN"
[925]468                else :   
[713]469                    action = "ALLOW"
[925]470        else :
[1041]471            if not userpquota.Exists :
472                # Unknown userquota
[956]473                if policy == "ALLOW" :
[925]474                    action = "POLICY_ALLOW"
[834]475                else :   
[925]476                    action = "POLICY_DENY"
[1041]477                self.logger.log_message(_("Unable to match user %s on printer %s, applying default policy (%s)") % (user.Name, printer.Name, action))
[925]478            else :   
[1041]479                pagecounter = int(userpquota.PageCounter or 0)
480                if userpquota.SoftLimit is not None :
481                    softlimit = int(userpquota.SoftLimit)
[925]482                    if pagecounter < softlimit :
483                        action = "ALLOW"
[834]484                    else :   
[1041]485                        if userpquota.HardLimit is None :
[925]486                            # only a soft limit, this is equivalent to having only a hard limit
487                            action = "DENY"
488                        else :   
[1041]489                            hardlimit = int(userpquota.HardLimit)
[925]490                            if softlimit <= pagecounter < hardlimit :   
491                                now = DateTime.now()
[1041]492                                if userpquota.DateLimit is not None :
493                                    datelimit = DateTime.ISO.ParseDateTime(userpquota.DateLimit)
[925]494                                else :
[1041]495                                    datelimit = now + self.config.getGraceDelay(printer.Name)
496                                    userpquota.setDateLimit(datelimit)
[925]497                                if now < datelimit :
498                                    action = "WARN"
499                                else :   
500                                    action = "DENY"
501                            else :         
[834]502                                action = "DENY"
[925]503                else :       
[1041]504                    if userpquota.HardLimit is not None :
[925]505                        # no soft limit, only a hard one.
[1041]506                        hardlimit = int(userpquota.HardLimit)
[925]507                        if pagecounter < hardlimit :
508                            action = "ALLOW"
509                        else :     
[742]510                            action = "DENY"
[925]511                    else :
512                        # Both are unset, no quota, i.e. accounting only
[834]513                        action = "ALLOW"
514        return action
[708]515   
[1192]516    def externalMailTo(self, cmd, action, user, printername, message) :
517        """Warns the user with an external command."""
518        username = user.Name
519        email = user.Email or user.Name
520        if "@" not in email :
521            email = "%s@%s" % (email, self.smtpserver)
522        os.system(cmd % locals())
523   
[1041]524    def warnGroupPQuota(self, grouppquota) :
[927]525        """Checks a group quota and send messages if quota is exceeded on current printer."""
[1041]526        group = grouppquota.Group
527        printer = grouppquota.Printer
528        admin = self.config.getAdmin(printer.Name)
529        adminmail = self.config.getAdminMail(printer.Name)
[1192]530        (mailto, arguments) = self.config.getMailTo(printer.Name)
[1068]531        action = self.checkGroupPQuota(grouppquota)
[927]532        if action.startswith("POLICY_") :
533            action = action[7:]
534        if action == "DENY" :
[1041]535            adminmessage = _("Print Quota exceeded for group %s on printer %s") % (group.Name, printer.Name)
[927]536            self.logger.log_message(adminmessage)
537            if mailto in [ "BOTH", "ADMIN" ] :
538                self.sendMessageToAdmin(adminmail, _("Print Quota"), adminmessage)
[1192]539            if mailto in [ "BOTH", "USER", "EXTERNAL" ] :
[1140]540                for user in self.storage.getGroupMembers(group) :
[1192]541                    if mailto != "EXTERNAL" :
542                        self.sendMessageToUser(admin, adminmail, user, _("Print Quota Exceeded"), self.config.getHardWarn(printer.Name))
543                    else :   
544                        self.externalMailTo(arguments, action, user, printer.Name, message)
[927]545        elif action == "WARN" :   
[1091]546            adminmessage = _("Print Quota low for group %s on printer %s") % (group.Name, printer.Name)
[927]547            self.logger.log_message(adminmessage)
548            if mailto in [ "BOTH", "ADMIN" ] :
549                self.sendMessageToAdmin(adminmail, _("Print Quota"), adminmessage)
[1077]550            if group.LimitBy and (group.LimitBy.lower() == "balance") : 
551                message = self.config.getPoorWarn()
552            else :     
553                message = self.config.getSoftWarn(printer.Name)
[1192]554            if mailto in [ "BOTH", "USER", "EXTERNAL" ] :
[1140]555                for user in self.storage.getGroupMembers(group) :
[1192]556                    if mailto != "EXTERNAL" :
557                        self.sendMessageToUser(admin, adminmail, user, _("Print Quota Exceeded"), message)
558                    else :   
559                        self.externalMailTo(arguments, action, user, printer.Name, message)
[927]560        return action       
[728]561       
[1041]562    def warnUserPQuota(self, userpquota) :
[728]563        """Checks a user quota and send him a message if quota is exceeded on current printer."""
[1041]564        user = userpquota.User
565        printer = userpquota.Printer
566        admin = self.config.getAdmin(printer.Name)
567        adminmail = self.config.getAdminMail(printer.Name)
[1192]568        (mailto, arguments) = self.config.getMailTo(printer.Name)
[1041]569        action = self.checkUserPQuota(userpquota)
[834]570        if action.startswith("POLICY_") :
571            action = action[7:]
[695]572        if action == "DENY" :
[1041]573            adminmessage = _("Print Quota exceeded for user %s on printer %s") % (user.Name, printer.Name)
[834]574            self.logger.log_message(adminmessage)
[1192]575            if mailto in [ "BOTH", "USER", "EXTERNAL" ] :
576                message = self.config.getHardWarn(printer.Name)
577                if mailto != "EXTERNAL" :
578                    self.sendMessageToUser(admin, adminmail, user, _("Print Quota Exceeded"), message)
579                else :   
580                    self.externalMailTo(arguments, action, user, printer.Name, message)
[852]581            if mailto in [ "BOTH", "ADMIN" ] :
582                self.sendMessageToAdmin(adminmail, _("Print Quota"), adminmessage)
[695]583        elif action == "WARN" :   
[1091]584            adminmessage = _("Print Quota low for user %s on printer %s") % (user.Name, printer.Name)
[695]585            self.logger.log_message(adminmessage)
[1192]586            if mailto in [ "BOTH", "USER", "EXTERNAL" ] :
[1077]587                if user.LimitBy and (user.LimitBy.lower() == "balance") : 
588                    message = self.config.getPoorWarn()
589                else :     
590                    message = self.config.getSoftWarn(printer.Name)
[1192]591                if mailto != "EXTERNAL" :   
592                    self.sendMessageToUser(admin, adminmail, user, _("Print Quota Low"), message)
593                else :   
594                    self.externalMailTo(arguments, action, user, printer.Name, message)
[852]595            if mailto in [ "BOTH", "ADMIN" ] :
596                self.sendMessageToAdmin(adminmail, _("Print Quota"), adminmessage)
[695]597        return action       
Note: See TracBrowser for help on using the browser.