diff options
| author | viega | 1998-05-30 06:06:53 +0000 |
|---|---|---|
| committer | viega | 1998-05-30 06:06:53 +0000 |
| commit | 4cf754ca57ef2e904082e105fcd4b78f97ed00a9 (patch) | |
| tree | a81db0267323e4200988d828cf3d83e206d06ce5 | |
| parent | bc98003ed7879c4bf32735750c2a1adf88fbe3d3 (diff) | |
| download | mailman-4cf754ca57ef2e904082e105fcd4b78f97ed00a9.tar.gz mailman-4cf754ca57ef2e904082e105fcd4b78f97ed00a9.tar.zst mailman-4cf754ca57ef2e904082e105fcd4b78f97ed00a9.zip | |
Integrated Scott's cookie code into the distribution.
Note that it does have one problem... If you have cookies off, you
have to log in every time, plus your changes don't take effect!
That definitely needs to be fixed.
| -rw-r--r-- | Mailman/MailList.py | 20 | ||||
| -rw-r--r-- | Mailman/Utils.py | 14 | ||||
| -rw-r--r-- | Mailman/htmlformat.py | 7 | ||||
| -rwxr-xr-x | cgi/admin | 390 | ||||
| -rwxr-xr-x | cgi/private | 22 | ||||
| -rw-r--r-- | modules/htmlformat.py | 7 | ||||
| -rw-r--r-- | modules/maillist.py | 20 | ||||
| -rw-r--r-- | modules/mm_utils.py | 14 |
8 files changed, 335 insertions, 159 deletions
diff --git a/Mailman/MailList.py b/Mailman/MailList.py index 44339b4d9..406d1e599 100644 --- a/Mailman/MailList.py +++ b/Mailman/MailList.py @@ -75,8 +75,13 @@ class MailList(MailCommandHandler, HTMLFormatter, Deliverer, ListAdmin, return '%s@%s' % (self._internal_name, self.host_name) def GetScriptURL(self, script_name): - return os.path.join(self.web_page_url, '%s/%s' % - (script_name, self._internal_name)) + if self.web_page_url: + prefix = self.web_page_url + else: + prefix = mm_cfg.DEFAULT_URL + return os.path.join(prefix, '%s/%s' % (script_name, + self._internal_name)) + def GetOptionsURL(self, addr, obscured=0): options = self.GetScriptURL('options') if obscured: @@ -85,9 +90,6 @@ class MailList(MailCommandHandler, HTMLFormatter, Deliverer, ListAdmin, treated = addr return os.path.join(options, treated) - def GetOptionsURL(self, addr): - return os.path.join(self.GetScriptURL('options'), addr) - def GetUserOption(self, user, option): if option == mm_cfg.Digests: return user in self.digest_members @@ -212,10 +214,10 @@ class MailList(MailCommandHandler, HTMLFormatter, Deliverer, ListAdmin, " what the list is."), ('info', mm_cfg.Text, (7, 50), 0, - 'An introductory description - a few paragraphs - about the' - 'list. It will be included, as html, at the top of the' - 'listinfo page. Carriage returns will end a paragraph - see' - 'the details for more info.', + ' An introductory description - a few paragraphs - about the' + ' list. It will be included, as html, at the top of the' + ' listinfo page. Carriage returns will end a paragraph - see' + ' the details for more info.', "The text will be treated as html <em>except</em> that newlines" " newlines will be translated to <br> - so you can use" diff --git a/Mailman/Utils.py b/Mailman/Utils.py index bc6b6a8da..419ae06a0 100644 --- a/Mailman/Utils.py +++ b/Mailman/Utils.py @@ -477,3 +477,17 @@ class StampedLogger(Logger): Logger.write(self, ' ' + l) else: Logger.write(self, l) + +def chunkify(members, chunksize=mm_cfg.ADMIN_MEMBER_CHUNKSIZE): + """ + return a list of lists of members + """ + members.sort() + res = [] + while 1: + if not members: + break + chunk = members[:chunksize] + res.append(chunk) + members = members[chunksize:] + return res diff --git a/Mailman/htmlformat.py b/Mailman/htmlformat.py index 587bb1a0a..ddb6ac229 100644 --- a/Mailman/htmlformat.py +++ b/Mailman/htmlformat.py @@ -20,7 +20,7 @@ Encapsulate HTML formatting directives in classes that act as containers for python and, recursively, for nested HTML formatting objects.""" -__version__ = "$Revision: 547 $" +__version__ = "$Revision: 635 $" # Eventually could abstract down to HtmlItem, which outputs an arbitrary html # object given start / end tags, valid options, and a value. @@ -406,6 +406,11 @@ class RadioButton(InputObj): def __init__(self, name, value, checked=0, **kws): apply(InputObj.__init__, (self, name, 'RADIO', value, checked), kws) +class CheckBox(InputObj): + def __init__(self, name, value, checked=0, **kws): + apply(InputObj.__init__, (self, name, "CHECKBOX", value, checked), kws) + + class VerticalSpacer: def __init__(self, size=10): @@ -21,12 +21,12 @@ To run stand-alone for debugging, set env var PATH_INFO to name of list and, optionally, options category.""" -__version__ = "$Revision: 564 $" +__version__ = "$Revision: 635 $" import sys -import os, cgi, string, crypt, types +import os, cgi, string, crypt, types, time import paths # path hacking -import mm_utils, maillist, mm_cfg, mm_err +import mm_utils, maillist, mm_cfg, mm_err, mm_mailcmd, Cookie from htmlformat import * try: @@ -44,12 +44,63 @@ CATEGORIES = [('general', "General Options"), ('archive', "Archival Options")] +LOGIN_PAGE = """ +<html> +<head> + <title>%(listname)s Administrative Authentication</title> +</head> +<body bgcolor="#ffffff"> +<FORM METHOD=POST ACTION="%(path)s"> +%(message)s + <TABLE WIDTH="100%%" BORDER="0" CELLSPACING="4" CELLPADDING="5"> + <TR> + <TD COLSPAN="2" WIDTH="100%%" BGCOLOR="#99CCFF" ALIGN="CENTER"> + <B><FONT COLOR="#000000" SIZE="+1">%(listname)s Administrative + Authentication</FONT></B> + </TD> + </TR> + <tr> + <TD> <div ALIGN="Right"> List Administrative Password: </div> </TD> + <TD> <INPUT TYPE=password NAME=adminpw SIZE=30></TD> + </tr> + <tr> + <td colspan=2 align=middle> <INPUT TYPE=SUBMIT name="request_login"> + </td> + </tr> + </TABLE> +</FORM> +""" + +# " <- icky emacs font-lock bug workaround + +def isAuthenticated(list, password=None): + if password is not None: # explicit login + try: + list.ConfirmAdminPassword(password) + except mm_err.MMBadPasswordError: + AddErrorMessage(doc, 'Error: Incorrect admin password.') + return 0 + else: + c = Cookie.Cookie() + c[list_name] = list.password # its crypted so this should be ok + c[list_name]['expires'] = mm_cfg.ADMIN_COOKIE_LIFE + c[list_name]["path"] = "/mailman/" + list.GetScriptURL("admin") + print c # Output the cookie + + return 1 + if os.environ.has_key('HTTP_COOKIE'): + c = Cookie.Cookie( os.environ['HTTP_COOKIE'] ) + if c.has_key(list_name): + return (c[list_name].value == list.password) + return 0 + + def main(): """Process and produce list options form. CGI input indicates that we're returning from submission of some new settings, which is processed before producing the new version.""" - global list_name, list_info + global list_name, list_info, doc doc = Document() try: @@ -81,26 +132,36 @@ def main(): if category not in map(lambda x: x[0], CATEGORIES): category = 'general' - + global cgi_data cgi_data = cgi.FieldStorage() - if len(cgi_data.keys()): - if cgi_data.has_key('VARHELP'): - FormatOptionHelp(doc, cgi_data['VARHELP'].value, lst) - print doc.Format(bgcolor="#ffffff") + is_auth = 0 + if cgi_data.has_key("adminpw"): + is_auth = isAuthenticated(lst, cgi_data["adminpw"].value) + message = FontAttr("Sorry, wrong password. Try again.", + color="ff5060", size="+1").Format() + else: + is_auth = isAuthenticated(lst) + message = "" + if not is_auth: + print "Content-type: text/html\n\n" + print LOGIN_PAGE % ({"listname": list_name, + "path": os.environ.get("REQUEST_URI", "/mailman/admin"), + "message": message}) + return + + if len(cgi_data.keys()): + if cgi_data.has_key('VARHELP'): + FormatOptionHelp(doc, cgi_data['VARHELP'].value, lst) + print doc.Format(bgcolor="#ffffff") return - if not cgi_data.has_key('adminpw'): - AddErrorMessage(doc, - 'Error: You must supply the admin password to' - ' change options.') - else: + if (cgi_data.has_key('bounce_matching_headers')): try: - lst.ConfirmAdminPassword(cgi_data['adminpw'].value) - ChangeOptions(lst, category, cgi_data, doc) - # Yuck. This shouldn't need to be here. - if not lst.digestable and not lst.nondigestable: - lst.nondigestable = 1 - except mm_err.MMBadPasswordError: - AddErrorMessage(doc, 'Error: Incorrect admin password.') + pairs = lst.parse_matching_header_opt() + except mm_err.MMBadConfigError, line: + AddErrorMessage(doc, + 'Warning: bad matching-header line' + ' (does it have the colon?)<ul> %s </ul>', + line) if not lst.digestable and len(lst.digest_members): AddErrorMessage(doc, @@ -113,17 +174,8 @@ def main(): ' but non-digestified mail is turned' ' off. They will receive mail until' ' you fix this problem.') - - if len(cgi_data.keys()): - if (cgi_data.has_key('bounce_matching_headers')): - try: - pairs = lst.parse_matching_header_opt() - except mm_err.MMBadConfigError, line: - AddErrorMessage(doc, - 'Warning: bad matching-header line' - ' (does it have the colon?)<ul> %s </ul>', - line) - + if len(cgi_data.keys()): + ChangeOptions(lst, category, cgi_data, doc) FormatConfiguration(doc, lst, category, category_suffix) print doc.Format(bgcolor="#ffffff") @@ -143,27 +195,13 @@ def FormatAdminOverview(error=None): table.AddCellInfo(max(table.GetCurrentRowIndex(), 0), 0, colspan=2, bgcolor="#99ccff") - # XXX We need a portable way to determine the host by which we are being - # visited! An absolute URL would do... - if os.environ.has_key('HTTP_HOST'): - http_host = os.environ['HTTP_HOST'] - else: - http_host = None - advertised = [] names = mm_utils.list_names() names.sort() for n in names: l = maillist.MailList(n) l.Unlock() - if l.advertised: - if (http_host - and (string.find(http_host, l.host_name) == -1 - and string.find(l.host_name, http_host) == -1)): - # List is for different identity for this host - skip it. - continue - else: - advertised.append(l) + if l.advertised: advertised.append(l) if error: greeting = FontAttr(error, color="ff5060", size="+1") @@ -247,12 +285,12 @@ def FormatConfiguration(doc, lst, category, category_suffix): other_links.AddItem(link) these_links = UnorderedList() - url = lst.GetScriptURL('admin') for k, v in CATEGORIES: if k == category: these_links.AddItem("<b> => " + v + " <= </b>") else: - these_links.AddItem(Link(os.path.join(url, k), v)) + these_links.AddItem(Link("%s/%s" % (lst.GetScriptURL('admin'),k), + v)) links_table.AddRow([these_links, other_links]) links_table.AddRowInfo(max(links_table.GetCurrentRowIndex(), 0), @@ -261,7 +299,8 @@ def FormatConfiguration(doc, lst, category, category_suffix): doc.AddItem(links_table) doc.AddItem('<hr>') if category_suffix: - form = Form(os.path.join(lst.GetScriptURL('admin'), category)) + form = Form("%s/%s" % (lst.GetScriptURL('admin'), + category_suffix)) else: form = Form(lst.GetScriptURL('admin')) doc.AddItem(form) @@ -452,53 +491,91 @@ def FormatMembershipOptions(lst): header.AddRow([Center(Header(2, "Membership Management"))]) header.AddCellInfo(max(header.GetCurrentRowIndex(), 0), 0, colspan=2, bgcolor="#99ccff") - header.AddRow([Bold("Subscribe and Unsubscribe Members")]) - header.AddCellInfo(max(header.GetCurrentRowIndex(), 0), 0, - colspan=2, bgcolor ="#FFF0D0") container.AddItem(header) - - container.AddItem('<h3>Mass Subscribe Members</h3>') - container.AddItem('<h4>Enter one email address per line</h4>') - container.AddItem(TextArea(name='subscribees', rows=20,cols=60,wrap=None)) + user_table = Table(width="90%") + user_table.AddRow([Center(Header(4, "Membership List"))]) + user_table.AddCellInfo(user_table.GetCurrentRowIndex(), + user_table.GetCurrentCellIndex(), + bgcolor="#cccccc", colspan=8) - container.AddItem('<h3>To Unsubscribe Members...</h3>') - container.AddItem(""" - To unsubscribe members you must use your admin password in place of the - user's password on the user's edit-options page. Visit their - edit-options page (via the <a href="%s">roster</a> page) and do the - unsubscribe procedure, providing the admin password instead of the - user's password. - <p>(Note that you can, alternately, set the subscriber's no-delivery - option to inhibit delivery of their messages, if you want to only - temporarily disable their delivery.)<p>""" - % lst.GetScriptURL('roster')) + members = {} + digests = {} + for member in lst.members: + members[member] = 1 + for member in lst.digest_members: + digests[member] = 1 + all = lst.members + lst.digest_members + if len(all) > mm_cfg.ADMIN_MEMBER_CHUNKSIZE: + chunks = mm_utils.chunkify(all) + if not cgi_data.has_key("chunk"): + chunk = 0 + else: + chunk = string.atoi(cgi_data["chunk"].value) + all = chunks[chunk] + footer = ("<p><em>To View other sections, " + "click on the appropriate range listed below</em>") + chunk_indices = range(len(chunks)) + chunk_indices.remove(chunk) + buttons = [] + pi = os.environ["PATH_INFO"] + for ci in chunk_indices: + start, end = chunks[ci][0], chunks[ci][-1] + buttons.append("<a href=/mailman/admin%s?chunk=%d> from %s to %s </a>" % \ + ( pi, ci, start, end)) + buttons = apply(UnorderedList, tuple(buttons)) + footer = footer + buttons.Format() + "<p>" + else: + all.sort() + footer = "<p>" + for member in all: + cells = [member + "<input type=hidden name=user value=%s>" % (member), + "subscribed " +CheckBox(member + "_subscribed", "on", 1).Format(), + ] + if members.get(member): + cells.append("digest " + CheckBox(member + "_digest", "off", 0).Format()) + else: + cells.append("digest " + CheckBox(member + "_digest", "on", 1).Format()) + for opt in ("hide", "nomail", "ack", "norcv", "plain"): + if lst.GetUserOption(member, mm_mailcmd.option_info[opt]): + value = "on" + checked = 1 + else: + value = "off" + checked = 0 + box = CheckBox("%s_%s" % (member, opt), value, checked) + cells.append("%s %s" % (opt, box.Format())) + user_table.AddRow(cells) + container.AddItem(Center(user_table)) + container.AddItem(footer) + t = Table(width="90%") + t.AddRow([Center(Header(4, "Mass Subscribe Members"))]) + t.AddCellInfo(t.GetCurrentRowIndex(), + t.GetCurrentCellIndex(), + bgcolor="#cccccc", colspan=8) + container.AddItem(Center(t)) + container.AddItem(Center(TextArea(name='subscribees', rows=10,cols=60,wrap=None))) + container.AddItem(Center("<em> Enter One address per line</em><p>")) return container def FormatPasswordStuff(): - password_submit = Table(bgcolor="#99cccc", - border=0, cellspacing=0, cellpadding=2) - password_submit.AddRow([Center(Bold('To Submit Your Changes'))]) - password_submit.AddCellInfo(password_submit.GetCurrentRowIndex(), 0, - colspan=2) - password_submit.AddRow(['<div ALIGN="right">Enter the administrator ' - 'password:</div>', - PasswordBox('adminpw')]) - password_submit.AddRow(['<div ALIGN="right">And...</div>', - Bold(SubmitButton('submit', 'Submit Changes'))]) - change_pw_table = Table(bgcolor="#cccccc", border=0, - cellspacing=0, cellpadding=2) - change_pw_table.AddRow([Bold(Center('To Change The Administrator' - ' Password'))]) - change_pw_table.AddCellInfo(change_pw_table.GetCurrentRowIndex(), 0, - colspan=2) - change_pw_table.AddRow(['<div ALIGN="right">Enter the new ' - 'password:</div>', - PasswordBox('newpw')]) - change_pw_table.AddRow(['<div ALIGN="right">And also confirm it:</div>', - PasswordBox('confirmpw')]) - - password_stuff = Table(bgcolor="#99cccc") - password_stuff.AddRow([password_submit, change_pw_table]) + submit = Table(bgcolor="#99ccff", + border=0, cellspacing=0, cellpadding=2, width="100%") + submit.AddRow([Bold(SubmitButton('submit', 'Submit Your Changes'))]) + submit.AddCellInfo(submit.GetCurrentRowIndex(), 0, align="middle") + change_pw_table = Table(bgcolor="#99cccc", border=0, + cellspacing=0, cellpadding=2, width="90%") + change_pw_table.AddRow([Bold(Center('To Change The Administrator Password')), + '<div ALIGN="right"> Enter the new password: </div>', + PasswordBox('newpw'),]) + change_pw_table.AddCellInfo(0, 0, align="middle", colspan=2) + change_pw_table.AddRow(['<div ALIGN="right"> Enter the current password </div>', + PasswordBox('adminpw'), + '<div ALIGN="right">Again to confirm it:</div>', + PasswordBox('confirmpw')]) + password_stuff = Container() + password_stuff.AddItem(change_pw_table) + password_stuff.AddItem("<p>") + password_stuff.AddItem(submit) return password_stuff # XXX klm - looks like turn_on_moderation is orphaned. @@ -582,7 +659,38 @@ def GetValidValue(lst, prop, my_type, val, dependant): def ChangeOptions(lst, category, cgi_info, document): dirty = 0 - if category != 'members': + confirmed = 0 + if cgi_info.has_key('newpw'): + if cgi_info.has_key('confirmpw'): + if cgi_info.has_key('adminpw'): + try: + lst.ConfirmAdminPassword(cgi_info['adminpw'].value) + confirmed = 1 + except mm_err.MMBadPasswordError: + m = "Error: incorrect administrator password" + document.AddItem(Header(3, Italic(FontAttr(m, color="ff5060")))) + confirmed = 0 + if confirmed: + new = cgi_info['newpw'].value + confirm = cgi_info['confirmpw'].value + if new == confirm: + lst.password = crypt.crypt(new, mm_utils.GetRandomSeed()) + dirty = 1 + else: + m = 'Error: Passwords did not match.' + document.AddItem(Header(3, Italic(FontAttr(m, color="ff5060")))) + + else: + m = 'Error: You must type in your new password twice.' + document.AddItem( + Header(3, Italic(FontAttr(m, color="ff5060")))) + # + # for some reason, the login page mangles important values for the list + # such as .real_name so we only process these changes if the category + # is not "members" and the request is not from the login page + # -scott 19980515 + # + if category != 'members' and not cgi_info.has_key("request_login"): opt_list = GetConfigOptions(lst, category) for item in opt_list: if len(item) < 5: @@ -600,37 +708,85 @@ def ChangeOptions(lst, category, cgi_info, document): val = cgi_info[property].value value = GetValidValue(lst, property, kind, val, deps) if getattr(lst, property) != value: + print "property: ", property, "value: ", value setattr(lst, property, value) dirty = 1 + # + # mass subscription processing for members category + # if cgi_info.has_key('subscribees'): name_text = cgi_info['subscribees'].value - names = string.split(name_text, '\r\n') - for new_name in names: + name_text = string.replace(name_text, '\r', '') + names = string.split(name_text, '\n') + if '' in names: + names.remove('') + subscribe_success = [] + subscribe_errors = [] + for new_name in map(string.strip,names): + digest = 0 + if not lst.digestable: + digest = 0 + if not lst.nondigestable: + digest = 1 try: -#FIXME: The admin needs to be able to specify subscribe options - lst.AddMember(new_name, (mm_utils.GetRandomSeed() + - mm_utils.GetRandomSeed())) - dirty = 1 -#FIXME: Give some sort of an indication of which names didn't work, -# and why they didn't work... - except: - pass - if cgi_info.has_key('newpw'): - if cgi_info.has_key('confirmpw'): - new = cgi_info['newpw'].value - confirm = cgi_info['confirmpw'].value - if new == confirm: - lst.password = crypt.crypt(new, mm_utils.GetRandomSeed()) + lst.ApprovedAddMember(new_name, (mm_utils.GetRandomSeed() + + mm_utils.GetRandomSeed()), digest) + subscribe_success.append(new_name) + except mm_err.MMAlreadyAMember: + subscribe_errors.append((new_name, 'Already a member')) + + except mm_err.MMBadEmailError: + subscribe_errors.append((new_name, "Bad/Invalid email address")) + except mm_err.MMHostileAddress: + subscribe_errors.append((new_name, "Hostile Address (illegal characters)")) + if subscribe_success: + document.AddItem(Header(5, "Successfully Subscribed:")) + document.AddItem(apply(UnorderedList, tuple((subscribe_success)))) + document.AddItem("<p>") + if subscribe_errors: + document.AddItem(Header(5, "Error Subscribing:")) + items = map(lambda x: "%s -- %s" % (x[0], x[1]), subscribe_errors) + document.AddItem(apply(UnorderedList, tuple((items)))) + document.AddItem("<p>") + # + # do the user options for members category + # + if cgi_info.has_key('user'): + user = cgi_info["user"] + if type(user) is type([]): + users = [] + for ui in range(len(user)): + users.append(user[ui].value) + else: + users = [user.value] + for user in users: + if not cgi_info.has_key('%s_subscribed' % (user)): + lst.DeleteMember(user) dirty = 1 - else: - m = 'Error: Passwords did not match.' - document.AddItem( - Header(3, Italic(FontAttr(m, color="ff5060")))) + continue + if not cgi_info.has_key("%s_digest" % (user)): + if user in lst.digest_members: + list.digest_members.remove(user) + dirty = 1 + if user not in lst.members: + lst.members.append(user) + dirty = 1 + else: + if user not in lst.digest_members: + lst.digest_members.append(user) + dirty = 1 + if user in lst.members: + lst.members.remove(user) + dirty = 1 + + for opt in ("hide", "nomail", "ack", "norcv", "plain"): + if cgi_info.has_key("%s_%s" % (user, opt)): + lst.SetUserOption(user, mm_mailcmd.option_info[opt], 1) + dirty = 1 + else: + lst.SetUserOption(user, mm_mailcmd.option_info[opt], 0) + dirty = 1 - else: - m = 'Error: You must type in your new password twice.' - document.AddItem( - Header(3, Italic(FontAttr(m, color="ff5060")))) if dirty: lst.Save() diff --git a/cgi/private b/cgi/private index 72d5c6031..facca81bc 100755 --- a/cgi/private +++ b/cgi/private @@ -74,7 +74,6 @@ PAGE = ''' login_attempted = 0 _list = None -# XXX: This looks broken, should investigate name_pat = re.compile(r""" (?: / (?: \d{4} q \d\. )? # Match "/", and, optionally, 1998q1." ( [^/]* ) /? # The SIG name @@ -88,20 +87,6 @@ name_pat = re.compile(r""" ) """, re.VERBOSE) -# " XXX: Emacs turd -# the following is a potentially better rewrite - -## name_pat = re.compile( -## r'(?: / (?: \d{4} q \d\. )?' # Match "/", and, optionally, 1998q1." -## r'( [^/]* ) /?' # The SIG name -## r'/[^/]*$' # The trailing 12345.html portion -## r') | (?:' -## r'/ ( [^/.]* )' # Match matrix-sig -## r'(?:\.html)?' # Optionally match .html -## r'/?' # Optionally match a trailing slash -## r'$)' # Must match to end of string -## , re.VERBOSE) - def getListName(path): match = name_pat.search(path) if match is None: return @@ -109,13 +94,6 @@ def getListName(path): if match.group(2): return match.group(2) raise ValueError, "Can't identify SIG name" -#for i in ['/matrix-sig.html', '/1998q1.c++-sig/index.html', -# '/1998q1.string-sig/foobar.html', -# '/psa-members.html']: -# print i, `getListName(i)` -#sys.exit(0) - -## sys.exit(0) def GetListobj(list_name): """Return an unlocked instance of the named maillist, if found.""" diff --git a/modules/htmlformat.py b/modules/htmlformat.py index 587bb1a0a..ddb6ac229 100644 --- a/modules/htmlformat.py +++ b/modules/htmlformat.py @@ -20,7 +20,7 @@ Encapsulate HTML formatting directives in classes that act as containers for python and, recursively, for nested HTML formatting objects.""" -__version__ = "$Revision: 547 $" +__version__ = "$Revision: 635 $" # Eventually could abstract down to HtmlItem, which outputs an arbitrary html # object given start / end tags, valid options, and a value. @@ -406,6 +406,11 @@ class RadioButton(InputObj): def __init__(self, name, value, checked=0, **kws): apply(InputObj.__init__, (self, name, 'RADIO', value, checked), kws) +class CheckBox(InputObj): + def __init__(self, name, value, checked=0, **kws): + apply(InputObj.__init__, (self, name, "CHECKBOX", value, checked), kws) + + class VerticalSpacer: def __init__(self, size=10): diff --git a/modules/maillist.py b/modules/maillist.py index 44339b4d9..406d1e599 100644 --- a/modules/maillist.py +++ b/modules/maillist.py @@ -75,8 +75,13 @@ class MailList(MailCommandHandler, HTMLFormatter, Deliverer, ListAdmin, return '%s@%s' % (self._internal_name, self.host_name) def GetScriptURL(self, script_name): - return os.path.join(self.web_page_url, '%s/%s' % - (script_name, self._internal_name)) + if self.web_page_url: + prefix = self.web_page_url + else: + prefix = mm_cfg.DEFAULT_URL + return os.path.join(prefix, '%s/%s' % (script_name, + self._internal_name)) + def GetOptionsURL(self, addr, obscured=0): options = self.GetScriptURL('options') if obscured: @@ -85,9 +90,6 @@ class MailList(MailCommandHandler, HTMLFormatter, Deliverer, ListAdmin, treated = addr return os.path.join(options, treated) - def GetOptionsURL(self, addr): - return os.path.join(self.GetScriptURL('options'), addr) - def GetUserOption(self, user, option): if option == mm_cfg.Digests: return user in self.digest_members @@ -212,10 +214,10 @@ class MailList(MailCommandHandler, HTMLFormatter, Deliverer, ListAdmin, " what the list is."), ('info', mm_cfg.Text, (7, 50), 0, - 'An introductory description - a few paragraphs - about the' - 'list. It will be included, as html, at the top of the' - 'listinfo page. Carriage returns will end a paragraph - see' - 'the details for more info.', + ' An introductory description - a few paragraphs - about the' + ' list. It will be included, as html, at the top of the' + ' listinfo page. Carriage returns will end a paragraph - see' + ' the details for more info.', "The text will be treated as html <em>except</em> that newlines" " newlines will be translated to <br> - so you can use" diff --git a/modules/mm_utils.py b/modules/mm_utils.py index bc6b6a8da..419ae06a0 100644 --- a/modules/mm_utils.py +++ b/modules/mm_utils.py @@ -477,3 +477,17 @@ class StampedLogger(Logger): Logger.write(self, ' ' + l) else: Logger.write(self, l) + +def chunkify(members, chunksize=mm_cfg.ADMIN_MEMBER_CHUNKSIZE): + """ + return a list of lists of members + """ + members.sort() + res = [] + while 1: + if not members: + break + chunk = members[:chunksize] + res.append(chunk) + members = members[chunksize:] + return res |
