[Mailman-Developers] unsubscriptions requiring approval
Thomas Wouters
thomas@xs4all.net
Tue, 8 Feb 2000 02:05:27 +0100
--ZPt4rx8FFjLCG7dd
Content-Type: text/plain; charset=us-ascii
Another minor patch adding what I need to mailman ;) I also noticed this on
the jitterbug list, so I guess more people had wished or at least wondered
for it ;P I'm posting it mostly to see if it generates comments, and for me
to find a good excuse to sleep. It's functional, it works on my test-list,
but some of it (most notably the english -- is there a good word for
'unsubscription request' ?) sucks.
Adding this has been mostly an exercise to learn a bit about the Mailman
insides, and to see how easily it can be done; fairly easily ;) Comments are
welcome, including "d'oh, dont write that, i already wrote it !"
The patchs misses the file 'templates/unsubauth.txt' by the way.. Just copy
subauth.txt and change 'subscr' into 'unsubscr' ;P
Sleepy-ly y'rs,
--
Thomas Wouters <thomas@xs4all.net>
Hi! I'm a .signature virus! copy me into your .signature file to help me spread!
--ZPt4rx8FFjLCG7dd
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="mailman-unsub-approval.diff"
? templates/unsubauth.txt
Index: Mailman/Bouncer.py
===================================================================
RCS file: /projects/cvsroot/mailman/Mailman/Bouncer.py,v
retrieving revision 1.37
diff -u -r1.37 Bouncer.py
--- Bouncer.py 1999/12/16 17:11:04 1.37
+++ Bouncer.py 2000/02/08 00:57:47
@@ -299,7 +299,7 @@
self.real_name, addr, reason)
return reason, 1
try:
- self.DeleteMember(addr, "bouncing addr")
+ self.ApprovedDeleteMember(addr, "bouncing addr")
self.LogMsg("bounce", "%s: removed %s", self.real_name, addr)
self.Save()
return 1, 1
Index: Mailman/Defaults.py.in
===================================================================
RCS file: /projects/cvsroot/mailman/Mailman/Defaults.py.in,v
retrieving revision 1.90
diff -u -r1.90 Defaults.py.in
--- Defaults.py.in 1999/11/30 23:10:57 1.90
+++ Defaults.py.in 2000/02/08 00:57:47
@@ -344,4 +344,4 @@
from Version import VERSION
# Data file version number
-DATA_FILE_VERSION = 15
+DATA_FILE_VERSION = 16
Index: Mailman/HTMLFormatter.py
===================================================================
RCS file: /projects/cvsroot/mailman/Mailman/HTMLFormatter.py,v
retrieving revision 1.46
diff -u -r1.46 HTMLFormatter.py
--- HTMLFormatter.py 1999/11/24 21:15:21 1.46
+++ HTMLFormatter.py 2000/02/08 00:57:47
@@ -206,6 +206,11 @@
" request will be sent to the '%s' account for"
" your address.)" % self.umbrella_member_suffix)
+ if self.unsubscribe_policy == 1:
+ msg = msg + ("<p>(Please note that this list does not allow"
+ " members to unsubscribe themselves without"
+ " administrator approval. Unsubscription requests"
+ " will get forwarded to the mailinglist administrator.)")
return msg
def FormatUndigestButton(self):
Index: Mailman/ListAdmin.py
===================================================================
RCS file: /projects/cvsroot/mailman/Mailman/ListAdmin.py,v
retrieving revision 1.27
diff -u -r1.27 ListAdmin.py
--- ListAdmin.py 1999/11/24 21:13:59 1.27
+++ ListAdmin.py 2000/02/08 00:57:48
@@ -40,6 +40,7 @@
# Request types requiring admin approval
HELDMSG = 1
SUBSCRIPTION = 2
+UNSUBSCRIPTION = 3
@@ -106,6 +107,9 @@
def GetSubscriptionIds(self):
return self.__getmsgids(SUBSCRIPTION)
+ def GetUnsubscriptionIds(self):
+ return self.__getmsgids(UNSUBSCRIPTION)
+
def GetRecord(self, id):
self.__opendb()
type, data = self.__db[id]
@@ -122,9 +126,11 @@
del self.__db[id]
if rtype == HELDMSG:
self.__handlepost(data, value, comment)
- else:
- assert rtype == SUBSCRIPTION
+ elif rtype == SUBSCRIPTION:
self.__handlesubscription(data, value, comment)
+ else:
+ assert rtype == UNSUBSCRIPTION
+ self.__handleunsubscription(data, value, comment)
def HoldMessage(self, msg, reason):
# assure that the database is open for writing
@@ -258,6 +264,53 @@
assert value == 1
self.ApprovedAddMember(addr, password, digest)
+ def HoldUnsubscription(self, addr, whence, admin_notif):
+ # assure that the database is open for writing
+ self.__opendb()
+ # get the next unique id
+ id = self.__request_id()
+ assert not self.__db.has_key(id)
+ #
+ # save the information to the request database. for held subscription
+ # entries, each record in the database will be one of the following
+ # format:
+ #
+ # the time the subscription request was received
+ # the subscriber's address
+ # the log message
+ # wether or not to send an unsubscription notify to the list admin
+ #
+ data = time.time(), addr, whence, admin_notif
+ self.__db[id] = (UNSUBSCRIPTION, data)
+ #
+ # TBD: this really shouldn't go here but I'm not sure where else is
+ # appropriate.
+ self.LogMsg('vette', '%s: held unsubscription request from %s' %
+ (self.real_name, addr))
+ # possibly notify the administrator
+ if self.admin_immed_notify:
+ subject = 'New unsubscription request to list %s from %s' % (
+ self.real_name, addr)
+ text = Utils.maketext(
+ 'unsubauth.txt',
+ {'username' : addr,
+ 'listname' : self.real_name,
+ 'hostname' : self.host_name,
+ 'admindb_url': self.GetAbsoluteScriptURL('admindb'),
+ })
+ adminaddr = self.GetAdminEmail()
+ msg = Message.UserNotification(adminaddr, adminaddr, subject, text)
+ HandlerAPI.DeliverToUser(self, msg)
+
+ def __handleunsubscription(self, record, value, comment):
+ stime, addr, whence, admin_notif = record
+ if value == 0:
+ # refused
+ self.__refuse('Unsubscription request', addr, comment)
+ else:
+ # subscribe
+ assert value == 1
+ self.ApprovedDeleteMember(addr, whence, admin_notif)
def __refuse(self, request, recip, comment, origmsg=None):
adminaddr = self.GetAdminEmail()
Index: Mailman/MailCommandHandler.py
===================================================================
RCS file: /projects/cvsroot/mailman/Mailman/MailCommandHandler.py,v
retrieving revision 1.59
diff -u -r1.59 MailCommandHandler.py
--- MailCommandHandler.py 1999/12/16 17:13:28 1.59
+++ MailCommandHandler.py 2000/02/08 00:57:48
@@ -500,6 +500,8 @@
self.ConfirmUserPassword(addr, args[0])
self.DeleteMember(addr, "mailcmd")
self.AddToResponse("Succeeded.")
+ except Errors.MMNeedApproval:
+ self.AddToResponse("Unsubscribing needs admininistrator approval.\nYour request has been forwarded to the list administrator.")
except Errors.MMListNotReady:
self.AddError("List is not functional.")
except (Errors.MMNoSuchUserError, Errors.MMNotAMemberError):
Index: Mailman/MailList.py
===================================================================
RCS file: /projects/cvsroot/mailman/Mailman/MailList.py,v
retrieving revision 1.151
diff -u -r1.151 MailList.py
--- MailList.py 1999/12/27 22:54:55 1.151
+++ MailList.py 2000/02/08 00:57:49
@@ -339,6 +339,7 @@
self.welcome_msg = ''
self.goodbye_msg = ''
self.subscribe_policy = mm_cfg.DEFAULT_SUBSCRIBE_POLICY
+ self.unsubscribe_policy = mm_cfg.DEFAULT_UNSUBSCRIBE_POLICY
self.private_roster = mm_cfg.DEFAULT_PRIVATE_ROSTER
self.obscure_addresses = mm_cfg.DEFAULT_OBSCURE_ADDRESSES
self.member_posting_only = mm_cfg.DEFAULT_MEMBER_POSTING_ONLY
@@ -599,6 +600,17 @@
sub_cfentry,
+ ('unsubscribe_policy', mm_cfg.Radio,
+ ('none', 'require approval'), 0,
+ "What steps are required for unsubscription?<br>",
+ "none - anyone can unsubscribe using email or the webpage<br>"
+ "require approval - require list administrator approval"
+ " for unsubscriptions<br>"
+ "<p>Please leave the unsubscription 'open' unless you have a"
+ " very good reason to require approval. Leaving people on lists"
+ " against their will can easily be seen as spamming or harassment"
+ ),
+
"Membership exposure",
('private_roster', mm_cfg.Radio,
@@ -1083,7 +1095,7 @@
HandlerAPI.DeliverToUser(self, msg)
return result
- def DeleteMember(self, name, whence=None, admin_notif=None):
+ def ApprovedDeleteMember(self, name, whence=None, admin_notif=None):
self.IsListInitialized()
# FindMatchingAddresses *should* never return more than 1 address.
# However, should log this, just to make sure.
@@ -1139,6 +1151,15 @@
whence = ""
self.LogMsg("subscribe", "%s: deleted %s%s",
self._internal_name, name, whence)
+
+ def DeleteMember(self, name, whence=None, admin_notif=None):
+ self.IsListInitialized()
+ # FindMatchingAddresses *should* never return more than 1 address.
+ # However, should log this, just to make sure.
+ if self.unsubscribe_policy == 1:
+ self.HoldUnsubscription(name, whence, admin_notif)
+ raise Errors.MMNeedApproval
+ ApprovedDeleteMember(name, whence, admin_notif)
def IsMember(self, address):
return len(Utils.FindMatchingAddresses(address, self.members,
Index: Mailman/Cgi/admin.py
===================================================================
RCS file: /projects/cvsroot/mailman/Mailman/Cgi/admin.py,v
retrieving revision 1.53
diff -u -r1.53 admin.py
--- admin.py 1999/12/27 22:28:07 1.53
+++ admin.py 2000/02/08 00:57:50
@@ -887,7 +887,7 @@
for user in users:
if not cgi_info.has_key('%s_subscribed' % (user)):
try:
- mlist.DeleteMember(user)
+ mlist.ApprovedDeleteMember(user)
dirty = 1
except Errors.MMNoSuchUserError:
unsubscribe_errors.append((user, 'Not subscribed'))
Index: Mailman/Cgi/admindb.py
===================================================================
RCS file: /projects/cvsroot/mailman/Mailman/Cgi/admindb.py,v
retrieving revision 1.19
diff -u -r1.19 admindb.py
--- admindb.py 1999/11/15 22:29:46 1.19
+++ admindb.py 2000/02/08 00:57:50
@@ -164,6 +164,19 @@
for id in subpendings:
PrintAddMemberRequest(mlist, id, t)
form.AddItem(t)
+ unsubpendings = mlist.GetUnsubscriptionIds()
+ if unsubpendings:
+ form.AddItem('<hr>')
+ form.AddItem(Center(Header(2, 'Unsubscription Requests')))
+ t = Table(border=2)
+ t.AddRow([
+ Bold('Address'),
+ Bold('Your Decision'),
+ Bold('Reason for unsubscription refusal (optional)')
+ ])
+ for id in unsubpendings:
+ PrintDeleteMemberRequest(mlist, id, t)
+ form.AddItem(t)
# Post holds are now handled differently
heldmsgs = mlist.GetHeldMessageIds()
total = len(heldmsgs)
@@ -183,6 +196,13 @@
time, addr, passwd, digest = mlist.GetRecord(id)
table.AddRow([addr,
RadioButtonArray(id, ('Refuse', 'Subscribe')),
+ TextBox('comment-%d' % id, size=30)
+ ])
+
+def PrintDeleteMemberRequest(mlist, id, table):
+ time, addr, whence, admin_notif = mlist.GetRecord(id)
+ table.AddRow([addr,
+ RadioButtonArray(id, ('Refuse', 'Unsubscribe')),
TextBox('comment-%d' % id, size=30)
])
Index: Mailman/Cgi/handle_opts.py
===================================================================
RCS file: /projects/cvsroot/mailman/Mailman/Cgi/handle_opts.py,v
retrieving revision 1.18
diff -u -r1.18 handle_opts.py
--- handle_opts.py 2000/01/21 20:30:51 1.18
+++ handle_opts.py 2000/02/08 00:57:50
@@ -101,6 +101,12 @@
pw = form["upw"].value
if mlist.ConfirmUserPassword(user, pw):
mlist.DeleteMember(user, "web cmd")
+ except Errors.MMNeedApproval:
+ PrintResults(mlist, operation, doc,
+ "Unsubscribing from this list requires "
+ "administrator approval. Your request has been "
+ "forwarded. Please contact the list administrator "
+ "if you have any questions.")
except Errors.MMListNotReady:
PrintResults(mlist, operation, doc, "List is not functional.")
except Errors.MMNoSuchUserError:
Index: bin/remove_members
===================================================================
RCS file: /projects/cvsroot/mailman/bin/remove_members,v
retrieving revision 1.3
diff -u -r1.3 remove_members
--- remove_members 1999/03/04 17:18:24 1.3
+++ remove_members 2000/02/08 00:57:50
@@ -100,7 +100,7 @@
for addr in addresses:
try:
- mlist.DeleteMember(addr)
+ mlist.ApprovedDeleteMember(addr)
except Errors.MMNoSuchUserError:
print "User `%s' not found." % addr
Index: bin/sync_members
===================================================================
RCS file: /projects/cvsroot/mailman/bin/sync_members,v
retrieving revision 1.8
diff -u -r1.8 sync_members
--- sync_members 1999/11/30 21:17:33 1.8
+++ sync_members 2000/02/08 00:57:50
@@ -244,7 +244,7 @@
for laddr, addr in addrs.items():
# should be a member, otherwise our test above is broken
if not dryrun:
- mlist.DeleteMember(addr, admin_notif=notifyadmin)
+ mlist.ApprovedDeleteMember(addr, admin_notif=notifyadmin)
print 'Removed: %30s (%30s)' % (laddr, addr)
mlist.Save()
--ZPt4rx8FFjLCG7dd--