[Mailman-developers] membership management developments: chunkifying
Scott
scott@chronis.icgroup.com
Tue, 21 Apr 1998 11:59:27 -0400
i have somehow managed to get the membership management section
chunkified, with proper authentication.
there is a new variable in mm_defaults called DEFAULT_CHUNK_SIZE,
which as commented sets the size of the chunk of members presented at
a time to admins editing their options/ unsubscribing them.
this section of membership management uses cookie authentication. As
Authentication is getting particulary messy in the program cgi/admin,
i wouldn't suggest (m)any futher develops that involve special
authentication without rewriting the script from the bottom up.
anyway, here is a patch, as applied against the distribution version
that i posted about the other day that does an improved job of
membership management stuff.
scott
Index: cgi/admin
===================================================================
RCS file: /usr/local/cvsroot/mailman/cgi/admin,v
retrieving revision 1.1.1.1
diff -c -r1.1.1.1 admin
*** admin 1998/04/21 03:27:57 1.1.1.1
--- admin 1998/04/21 15:45:15
***************
*** 9,17 ****
--- 9,20 ----
import sys
sys.path.append('/home/mailman/mailman/modules')
+ # XXX need to fix this, this directory should be configurable.
+ sys.path.append('/home/mailman/cgi-bin')
import os, cgi, string, crypt, types
import mm_utils, maillist, mm_cfg, mm_err
from htmlformat import *
+ import Cookie
try:
sys.stderr = mm_utils.StampedLogger("error", label = 'admin',
***************
*** 27,32 ****
--- 30,38 ----
('bounce', "Bounce Options"),
('archive', "Archival Options")]
+ #
+ # XXX this this data is also in mm_mailcmd (i think), it should become shared
+ #
option_info = { 'digest' : 0,
'nomail' : mm_cfg.DisableDelivery,
'norcv' : mm_cfg.DontReceiveOwnPosts,
***************
*** 36,42 ****
}
-
def main():
"""Process and produce list options form.
--- 42,47 ----
***************
*** 80,86 ****
FormatOptionHelp(doc, cgi_data['VARHELP'].value, list)
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.')
--- 85,125 ----
FormatOptionHelp(doc, cgi_data['VARHELP'].value, list)
print doc.Format(bgcolor="#ffffff")
return
! if cgi_data.has_key('adminpw') and cgi_data.has_key('edit_members'):
! adminpw = cgi_data["adminpw"]
! if type(adminpw) is type([]):
! adminpw = adminpw[0].value # use the first one, i guess -scott cotton
! else:
! adminpw = adminpw.value
! try:
! list.ConfirmAdminPassword(adminpw)
! FormatConfiguration(doc, list, category, category_suffix, cgi_data, 0)
! c = Cookie.Cookie()
! c[list.real_name] = hash(list.real_name)
! print c
! except mm_err.MMBadPasswordError:
! AddErrorMessage(doc, 'Error: Incorrect admin password.')
! FormatConfiguration(doc, list, category, category_suffix, cgi_data, 1)
! print doc.Format(bgcolor="#ffffff")
! return
! elif cgi_data.has_key('chunk'):
! if os.environ.has_key("HTTP_COOKIE"):
! cookie = Cookie.Cookie(os.environ["HTTP_COOKIE"])
! if cookie.has_key(list.real_name):
! if cookie[list.real_name].value == hash(list.real_name):
! FormatConfiguration(doc, list, category, category_suffix, cgi_data, 0)
! print doc.Format(bgcolor="#ffffff")
! return
! else:
! AddErrorMessage(doc, "Error: Invalid Cookie value")
! else:
! AddErrorMessage(doc, "Error, no cookie for list!")
! else:
! AddErrorMessage(doc, "no cookie!")
! FormatConfiguration(doc, list, category, category_suffix, cgi_data)
! print doc.Format(bgcolor="#ffffff")
! return
! elif not cgi_data.has_key('adminpw') :
AddErrorMessage(doc,
'Error: You must supply the admin password to'
' change options.')
***************
*** 90,103 ****
adminpw = adminpw[0].value # use the first one, i guess -scott cotton
else:
adminpw = adminpw.value
! try:
list.ConfirmAdminPassword(adminpw)
! ChangeOptions(list, category, cgi_data, doc)
# Yuck. This shouldn't need to be here. WTF?!?
! if not list.digestable and not list.nondigestable:
list.nondigestable = 1
! except mm_err.MMBadPasswordError:
! AddErrorMessage(doc, 'Error: Incorrect admin password.')
if not list.digestable and len(list.digest_members):
AddErrorMessage(doc,
--- 129,142 ----
adminpw = adminpw[0].value # use the first one, i guess -scott cotton
else:
adminpw = adminpw.value
! try:
list.ConfirmAdminPassword(adminpw)
! ChangeOptions(list, category, cgi_data, doc)
# Yuck. This shouldn't need to be here. WTF?!?
! if not list.digestable and not list.nondigestable:
list.nondigestable = 1
! except mm_err.MMBadPasswordError:
! AddErrorMessage(doc, 'Error: Incorrect admin password.')
if not list.digestable and len(list.digest_members):
AddErrorMessage(doc,
***************
*** 121,127 ****
' (does it have the colon?)<ul> %s </ul>',
line)
! FormatConfiguration(doc, list, category, category_suffix, cgi_data)
print doc.Format(bgcolor="#ffffff")
finally:
--- 160,169 ----
' (does it have the colon?)<ul> %s </ul>',
line)
! if cgi_data.has_key('edit_member_options'): # passwd already checked
! FormatConfiguration(doc, list, category, category_suffix, cgi_data, 0)
! else:
! FormatConfiguration(doc, list, category, category_suffix, cgi_data)
print doc.Format(bgcolor="#ffffff")
finally:
***************
*** 204,210 ****
print doc.Format(bgcolor="#ffffff")
! def FormatConfiguration(doc, list, category, category_suffix, cgi_data):
"""Produce the overall doc, *except* any processing error messages."""
for k, v in CATEGORIES:
if k == category: label = v
--- 246,252 ----
print doc.Format(bgcolor="#ffffff")
! def FormatConfiguration(doc, list, category, category_suffix, cgi_data, front_page=1):
"""Produce the overall doc, *except* any processing error messages."""
for k, v in CATEGORIES:
if k == category: label = v
***************
*** 253,269 ****
" bottom. (You can also change your password there,"
" as well.)<p>")
! form.AddItem(FormatOptionsSection(category, list, cgi_data))
form.AddItem(Center(FormatPasswordStuff()))
form.AddItem(list.GetMailmanFooter())
! def FormatOptionsSection(category, list, cgi_data):
"""Produce the category-specific options table."""
if category == 'members':
# Special case for members section.
! return FormatMembershipOptions(list, cgi_data)
options = GetConfigOptions(list, category)
big_table = Table(cellspacing=3, cellpadding=4)
--- 295,311 ----
" bottom. (You can also change your password there,"
" as well.)<p>")
! form.AddItem(FormatOptionsSection(category, list, cgi_data, front_page))
form.AddItem(Center(FormatPasswordStuff()))
form.AddItem(list.GetMailmanFooter())
! def FormatOptionsSection(category, list, cgi_data, front_page):
"""Produce the category-specific options table."""
if category == 'members':
# Special case for members section.
! return FormatMembershipOptions(list, cgi_data, front_page)
options = GetConfigOptions(list, category)
big_table = Table(cellspacing=3, cellpadding=4)
***************
*** 398,404 ****
descr = descr + "</div>"
return [descr, gui_part]
! def FormatMembershipOptions(list, cgi_data, option_info=option_info):
container = Container()
header = Table(width="100%")
header.AddRow([Center(Header(2, "Membership Management"))])
--- 440,446 ----
descr = descr + "</div>"
return [descr, gui_part]
! def FormatMembershipOptions(list, cgi_data, front_page=1, option_info=option_info):
container = Container()
header = Table(width="100%")
header.AddRow([Center(Header(2, "Membership Management"))])
***************
*** 408,428 ****
header.AddCellInfo(max(header.GetCurrentRowIndex(), 0), 0,
colspan=2, bgcolor ="#FFF0D0")
container.AddItem(header)
! if cgi_data.has_key("adminpw"):
! adminpw = cgi_data["adminpw"]
! if type(adminpw) is type([]): # admin password specified more than once
! adminpw = adminpw[0].value # use the first one, i guess
! else:
! adminpw = adminpw.value
! try:
! list.ConfirmAdminPassword(adminpw)
! confirmed = 1
! except (mm_err.MMBadPasswordError):
! confirmed = 0
! else:
! confirmed = 0
! if not cgi_data.has_key('edit_members') or (cgi_data.has_key('edit_members') and
! not confirmed):
container.AddItem(Center('\n<h3>To Unsubscribe Members or Edit their Options...</h3>'))
container.AddItem(Center("""
Enter the adminitrative password: <input type=password name=adminpw size=10>
--- 450,456 ----
header.AddCellInfo(max(header.GetCurrentRowIndex(), 0), 0,
colspan=2, bgcolor ="#FFF0D0")
container.AddItem(header)
! if front_page:
container.AddItem(Center('\n<h3>To Unsubscribe Members or Edit their Options...</h3>'))
container.AddItem(Center("""
Enter the adminitrative password: <input type=password name=adminpw size=10>
***************
*** 435,465 ****
else:
user_table = Table(width="90%")
digest_user_table = Table(width="90%")
! user_table.AddRow([Center(Header(4, "Members"))])
user_table.AddCellInfo(user_table.GetCurrentRowIndex(),
user_table.GetCurrentCellIndex(),
bgcolor="#cccccc", colspan=8)
for member in list.members:
! cells = [member,
! "subscribed " +CheckBox(member + "_subscribed", "on", 1).Format(),
! "digest " + CheckBox(member+"_digest", "off").Format(),
! ]
! for opt in ("hide", "nomail", "ack", "norcv", "plain"):
! if list.GetUserOption(member, 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)
for member in list.digest_members:
! cells = [member,
"subscribed " +CheckBox(member + "_subscribed", "on", 1).Format(),
- "digest " + CheckBox(member+"_digest", "on", 1).Format(),
]
for opt in ("hide", "nomail", "ack", "norcv", "plain"):
if list.GetUserOption(member, option_info[opt]):
value = "on"
--- 463,508 ----
else:
user_table = Table(width="90%")
digest_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)
+ members = {}
+ digests = {}
for member in list.members:
! members[member] = 1
for member in list.digest_members:
! digests[member] = 1
! all = list.members + list.digest_members
! if len(all) > mm_cfg.DEFAULT_CHUNK_SIZE:
! 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 list.GetUserOption(member, option_info[opt]):
value = "on"
***************
*** 471,483 ****
cells.append("%s %s" % (opt, box.Format()))
user_table.AddRow(cells)
container.AddItem(Center(user_table))
# trigger member list viewing again and change_options with hidden tags...
# password authentication is still done, so this should be safe
! container.AddItem("<input type=hidden name=edit_member_options value=on>"
! "<input type=hidden name=edit_members value=on><p>")
! container.AddItem("Back to <a href=/mailman/admin/%s/members>Mass Subscribing</a><p>" % \
! (list.real_name))
!
return container
def FormatPasswordStuff():
--- 514,529 ----
cells.append("%s %s" % (opt, box.Format()))
user_table.AddRow(cells)
container.AddItem(Center(user_table))
+ t = Table(width="90%")
+ t.AddRow([Center(Header(4, "<a href=/mailman/admin/%s/members>Back to Mass Subscriptions</a>" % list.real_name))])
+ t.AddCellInfo(t.GetCurrentRowIndex(),
+ t.GetCurrentCellIndex(),
+ bgcolor="#cccccc", colspan=8)
+ container.AddItem(Center(t))
+ container.AddItem(footer)
# trigger member list viewing again and change_options with hidden tags...
# password authentication is still done, so this should be safe
! container.AddItem("<input type=hidden name=edit_member_options value=on>")
return container
def FormatPasswordStuff():
***************
*** 590,620 ****
if getattr(list, property) != value:
setattr(list, property, value)
dirty = 1
- elif cgi_info.has_key('edit_member_options'):
- members = []
- digest_members = []
- org_user_opts = list.user_options.copy()
- for member in (list.members + list.digest_members)[:]:
- if not cgi_info.has_key(member + "_subscribed"):
- list.DeleteMember(member, "cgi: admin/members")
- continue
- if not cgi_info.has_key(member + "_digest"): # no digest
- members.append(member)
- else:
- digest_members.append(member)
- for opt in ("hide", "nomail", "ack", "norcv", "plain"):
- if cgi_info.has_key("%s_%s" % (member, opt)):
- list.SetUserOption(member, option_info[opt], 1)
- else:
- list.SetUserOption(member, option_info[opt], 0)
- if (list.user_options != org_user_opts):
- dirty = 1
- if list.members != members:
- list.members = members
- dirty = 1
- if list.digest_members != digest_members:
- list.digest_members = digest_members
- dirty = 1
elif cgi_info.has_key('subscribees'):
name_text = cgi_info['subscribees'].value
name_text = string.replace(name_text, '\r', '')
--- 636,641 ----
***************
*** 644,649 ****
--- 665,705 ----
items = map(lambda x: "%s -- %s" % (x[0], x[1]), subscribe_errors)
document.AddItem(apply(UnorderedList, tuple((items))))
document.AddItem("<p>")
+ elif 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)):
+ list.DeleteMember(user)
+ dirty = 1
+ continue
+ if not cgi_info.has_key("%s_digest" % (user)):
+ if user in list.digest_members:
+ list.digest_members.remove(user)
+ dirty = 1
+ if user not in list.members:
+ list.members.append(user)
+ dirty = 1
+ else:
+ if user not in list.digest_members:
+ list.digest_members.append(user)
+ dirty = 1
+ if user in list.members:
+ list.members.remove(user)
+ dirty = 1
+
+ for opt in ("hide", "nomail", "ack", "norcv", "plain"):
+ if cgi_info.has_key("%s_%s" % (user, opt)):
+ list.SetUserOption(user, option_info[opt], 1)
+ dirty = 1
+ else:
+ list.SetUserOption(user, option_info[opt], 0)
+ dirty = 1
if cgi_info.has_key('newpw'):
if cgi_info.has_key('confirmpw'):
new = cgi_info['newpw'].value
***************
*** 661,667 ****
m = 'Error: You must type in your new password twice.'
document.AddItem(
Header(3, Italic(FontAttr(m, color="ff5060"))))
!
if dirty:
list.Save()
--- 717,723 ----
m = 'Error: You must type in your new password twice.'
document.AddItem(
Header(3, Italic(FontAttr(m, color="ff5060"))))
!
if dirty:
list.Save()
Index: modules/mm_defaults.py
===================================================================
RCS file: /usr/local/cvsroot/mailman/modules/mm_defaults.py,v
retrieving revision 1.1.1.1
diff -c -r1.1.1.1 mm_defaults.py
*** mm_defaults.py 1998/04/21 03:27:58 1.1.1.1
--- mm_defaults.py 1998/04/21 15:52:05
***************
*** 37,42 ****
--- 37,48 ----
HOME_PAGE = 'index.html'
MAILMAN_OWNER = 'mailman-owner@%s' % DEFAULT_HOST_NAME
+ #
+ # break up presentation of editing member options under admin/listname/members
+ # into chunks of this size.
+ #
+ DEFAULT_CHUNK_SIZE=20
+
# System ceiling on number of batches into which deliveries are divided:
MAX_SPAWNS = 40
Index: modules/mm_utils.py
===================================================================
RCS file: /usr/local/cvsroot/mailman/modules/mm_utils.py,v
retrieving revision 1.1.1.1
diff -c -r1.1.1.1 mm_utils.py
*** mm_utils.py 1998/04/21 03:27:58 1.1.1.1
--- mm_utils.py 1998/04/21 08:27:54
***************
*** 394,400 ****
--- 394,418 ----
Logger.write(self, l)
+ def chunkify(members, chunksize=mm_cfg.DEFAULT_CHUNK_SIZE):
+ """
+ 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
+
+
+
+
+