summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Mailman/MailList.py20
-rw-r--r--Mailman/Utils.py14
-rw-r--r--Mailman/htmlformat.py7
-rwxr-xr-xcgi/admin390
-rwxr-xr-xcgi/private22
-rw-r--r--modules/htmlformat.py7
-rw-r--r--modules/maillist.py20
-rw-r--r--modules/mm_utils.py14
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 &lt;br&gt; - 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):
diff --git a/cgi/admin b/cgi/admin
index 69f3d9b5d..b0c7457db 100755
--- a/cgi/admin
+++ b/cgi/admin
@@ -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> =&gt; " + v + " &lt;= </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 &lt;br&gt; - 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