diff options
Diffstat (limited to 'Mailman/Gui/GUIBase.py')
-rw-r--r-- | Mailman/Gui/GUIBase.py | 200 |
1 files changed, 200 insertions, 0 deletions
diff --git a/Mailman/Gui/GUIBase.py b/Mailman/Gui/GUIBase.py new file mode 100644 index 00000000..7062235e --- /dev/null +++ b/Mailman/Gui/GUIBase.py @@ -0,0 +1,200 @@ +# Copyright (C) 2002 by the Free Software Foundation, Inc. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +"""Base class for all web GUI components.""" + +import re +from types import TupleType, ListType + +from Mailman import mm_cfg +from Mailman import Utils +from Mailman import Errors +from Mailman.i18n import _ + +NL = '\n' +BADJOINER = '</code>, <code>' + + + +class GUIBase: + # Providing a common interface for GUI component form processing. Most + # GUI components won't need to override anything, but some may want to + # override _setValue() to provide some specialized processing for some + # attributes. + def _getValidValue(self, mlist, property, wtype, val): + # Coerce and validate the new value. + # + # Radio buttons and boolean toggles both have integral type + if wtype in (mm_cfg.Radio, mm_cfg.Toggle): + # Let ValueErrors propagate + return int(val) + # String and Text widgets both just return their values verbatim + if wtype in (mm_cfg.String, mm_cfg.Text): + return val + # This widget contains a single email address + if wtype == mm_cfg.Email: + # BAW: We must allow blank values otherwise reply_to_address can't + # be cleared. This is currently the only mm_cfg.Email type widget + # in the interface, so watch out if we ever add any new ones. + if val: + # Let MMBadEmailError and MMHostileAddress propagate + Utils.ValidateEmail(val) + return val + # These widget types contain lists of email addresses, one per line. + # The EmailListEx allows each line to contain either an email address + # or a regular expression + if wtype in (mm_cfg.EmailList, mm_cfg.EmailListEx): + # BAW: value might already be a list, if this is coming from + # config_list input. Sigh. + if isinstance(val, ListType): + return val + addrs = [] + for addr in [s.strip() for s in val.split(NL)]: + # Discard empty lines + if not addr: + continue + try: + # This throws an exception if the address is invalid + Utils.ValidateEmail(addr) + except Errors.EmailAddressError: + # See if this is a context that accepts regular + # expressions, and that the re is legal + if wtype == mm_cfg.EmailListEx and addr.startswith('^'): + try: + re.compile(addr) + except re.error: + raise ValueError + else: + raise + addrs.append(addr) + return addrs + # This is a host name, i.e. verbatim + if wtype == mm_cfg.Host: + return val + # This is a number, either a float or an integer + if wtype == mm_cfg.Number: + num = -1 + try: + num = int(val) + except ValueError: + # Let ValueErrors percolate up + num = float(val) + if num < 0: + return getattr(mlist, property) + return num + # This widget is a select box, i.e. verbatim + if wtype == mm_cfg.Select: + return val + # Checkboxes return a list of the selected items, even if only one is + # selected. + if wtype == mm_cfg.Checkbox: + if isinstance(val, ListType): + return val + return [val] + if wtype == mm_cfg.FileUpload: + return val + if wtype == mm_cfg.Topics: + return val + # Should never get here + assert 0, 'Bad gui widget type: %s' % wtype + + def _setValue(self, mlist, property, val, doc): + # Set the value, or override to take special action on the property + if not property.startswith('_') and getattr(mlist, property) <> val: + setattr(mlist, property, val) + + def _postValidate(self, mlist, doc): + # Validate all the attributes for this category + pass + + def handleForm(self, mlist, category, subcat, cgidata, doc): + for item in self.GetConfigInfo(mlist, category, subcat): + # Skip descriptions and legacy non-attributes + if not isinstance(item, TupleType) or len(item) < 5: + continue + # Unpack the gui item description + property, wtype, args, deps, desc = item[0:5] + # BAW: I know this code is a little crufty but I wanted to + # reproduce the semantics of the original code in admin.py as + # closely as possible, for now. We can clean it up later. + # + # The property may be uploadable... + uploadprop = property + '_upload' + if cgidata.has_key(uploadprop) and cgidata[uploadprop].value: + val = cgidata[uploadprop].value + elif not cgidata.has_key(property): + continue + elif isinstance(cgidata[property], ListType): + val = [x.value for x in cgidata[property]] + else: + val = cgidata[property].value + # Coerce the value to the expected type, raising exceptions if the + # value is invalid + try: + val = self._getValidValue(mlist, property, wtype, val) + except ValueError: + doc.addError(_('Invalid value for variable: %(property)s')) + # This is the parent of MMBadEmailError and MMHostileAddress + except Errors.EmailAddressError: + doc.addError( + _('Bad email address for option %(property)s: %(val)s')) + else: + # Set the attribute, which will normally delegate to the mlist + self._setValue(mlist, property, val, doc) + # Do a final sweep once all the attributes have been set. This is how + # we can do cross-attribute assertions + self._postValidate(mlist, doc) + + # Convenience method for handling $-string attributes + def _convertString(self, mlist, property, alloweds, val, doc): + # Is the list using $-strings? + dollarp = getattr(mlist, 'use_dollar_strings', 0) + if dollarp: + ids = Utils.dollar_identifiers(val) + else: + # %-strings + ids = Utils.percent_identifiers(val) + # Here's the list of allowable interpolations + for allowed in alloweds: + if ids.has_key(allowed): + del ids[allowed] + if ids: + # What's left are not allowed + badkeys = ids.keys() + badkeys.sort() + bad = BADJOINER.join(badkeys) + doc.addError(_( + """The following illegal substitution variables were + found in the <code>%(property)s</code> string: + <code>%(bad)s</code> + <p>Your list may not operate properly until you correct this + problem."""), tag=_('Warning: ')) + return val + # Now if we're still using %-strings, do a roundtrip conversion and + # see if the converted value is the same as the new value. If not, + # then they probably left off a trailing `s'. We'll warn them and use + # the corrected string. + if not dollarp: + fixed = Utils.to_percent(Utils.to_dollar(val)) + if fixed <> val: + doc.addError(_( + """Your <code>%(property)s</code> string appeared to + have some correctable problems in its new value. + The fixed value will be used instead. Please + double check that this is what you intended. + """)) + return fixed + return val |