summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBarry Warsaw2007-09-16 12:08:24 -0400
committerBarry Warsaw2007-09-16 12:08:24 -0400
commit0d623d7684e2ad8309219da934fc956a498e3a71 (patch)
tree8e0e5430aae3b02078cf1a921905c420623980d7
parent0a3e802568a0bfaf9ceade4614a52db9a90b905c (diff)
downloadmailman-0d623d7684e2ad8309219da934fc956a498e3a71.tar.gz
mailman-0d623d7684e2ad8309219da934fc956a498e3a71.tar.zst
mailman-0d623d7684e2ad8309219da934fc956a498e3a71.zip
Finish up the request hold conversion. ListAdmin can go away though
it hasn't yet. SendSubscribeAck(), SendUnsubscribeAck(), and ApprovedDeleteMember() are all removed, though the latter is not yet completely eradicated.
-rw-r--r--Mailman/Deliverer.py47
-rw-r--r--Mailman/MailList.py31
-rw-r--r--Mailman/app/membership.py40
-rw-r--r--Mailman/app/moderator.py124
-rw-r--r--Mailman/docs/requests.txt183
-rw-r--r--Mailman/templates/en/adminunsubscribeack.txt1
-rw-r--r--Mailman/templates/en/unsubauth.txt4
7 files changed, 262 insertions, 168 deletions
diff --git a/Mailman/Deliverer.py b/Mailman/Deliverer.py
index c9b246400..c9b044286 100644
--- a/Mailman/Deliverer.py
+++ b/Mailman/Deliverer.py
@@ -37,53 +37,6 @@ mlog = logging.getLogger('mailman.mischief')
class Deliverer:
- def SendSubscribeAck(self, name, password, digest, text=''):
- pluser = self.getMemberLanguage(name)
- if self.welcome_msg:
- welcome = Utils.wrap(self.welcome_msg) + '\n'
- else:
- welcome = ''
- if self.umbrella_list:
- addr = self.GetMemberAdminEmail(name)
- umbrella = Utils.wrap(_('''\
-Note: Since this is a list of mailing lists, administrative
-notices like the password reminder will be sent to
-your membership administrative address, %(addr)s.'''))
- else:
- umbrella = ''
- # get the text from the template
- text += Utils.maketext(
- 'subscribeack.txt',
- {'real_name' : self.real_name,
- 'host_name' : self.host_name,
- 'welcome' : welcome,
- 'umbrella' : umbrella,
- 'emailaddr' : self.GetListEmail(),
- 'listinfo_url': self.GetScriptURL('listinfo', absolute=True),
- 'optionsurl' : self.GetOptionsURL(name, absolute=True),
- 'password' : password,
- 'user' : self.getMemberCPAddress(name),
- }, lang=pluser, mlist=self)
- if digest:
- digmode = _(' (Digest mode)')
- else:
- digmode = ''
- realname = self.real_name
- msg = Message.UserNotification(
- self.GetMemberAdminEmail(name), self.GetRequestEmail(),
- _('Welcome to the "%(realname)s" mailing list%(digmode)s'),
- text, pluser)
- msg['X-No-Archive'] = 'yes'
- msg.send(self, verp=config.VERP_PERSONALIZED_DELIVERIES)
-
- def SendUnsubscribeAck(self, addr, lang):
- realname = self.real_name
- msg = Message.UserNotification(
- self.GetMemberAdminEmail(addr), self.GetBouncesEmail(),
- _('You have been unsubscribed from the %(realname)s mailing list'),
- Utils.wrap(self.goodbye_msg), lang)
- msg.send(self, verp=config.VERP_PERSONALIZED_DELIVERIES)
-
def MailUserPassword(self, user):
listfullname = self.fqdn_listname
requestaddr = self.GetRequestEmail()
diff --git a/Mailman/MailList.py b/Mailman/MailList.py
index cbcaab275..80d946634 100644
--- a/Mailman/MailList.py
+++ b/Mailman/MailList.py
@@ -597,37 +597,6 @@ class MailList(object, HTMLFormatter, Deliverer,
raise Errors.MMNeedApproval, _(
'unsubscriptions require moderator approval')
- def ApprovedDeleteMember(self, name, whence=None,
- admin_notif=None, userack=None):
- if userack is None:
- userack = self.send_goodbye_msg
- if admin_notif is None:
- admin_notif = self.admin_notify_mchanges
- # Delete a member, for which we know the approval has been made
- fullname, emailaddr = parseaddr(name)
- userlang = self.getMemberLanguage(emailaddr)
- # Remove the member
- self.removeMember(emailaddr)
- # And send an acknowledgement to the user...
- if userack:
- self.SendUnsubscribeAck(emailaddr, userlang)
- # ...and to the administrator
- if admin_notif:
- realname = self.real_name
- subject = _('%(realname)s unsubscribe notification')
- text = Utils.maketext(
- 'adminunsubscribeack.txt',
- {'member' : name,
- 'listname': self.real_name,
- }, mlist=self)
- msg = Message.OwnerNotification(self, subject, text)
- msg.send(self)
- if whence:
- whence = "; %s" % whence
- else:
- whence = ""
- slog.info('%s: deleted %s%s', self.internal_name(), name, whence)
-
def ChangeMemberName(self, addr, name, globally):
self.setMemberName(addr, name)
if not globally:
diff --git a/Mailman/app/membership.py b/Mailman/app/membership.py
index abd2778f6..695366b91 100644
--- a/Mailman/app/membership.py
+++ b/Mailman/app/membership.py
@@ -151,3 +151,43 @@ def send_welcome_message(mlist, address, language, delivery_mode, text=''):
text, language)
msg['X-No-Archive'] = 'yes'
msg.send(mlist, verp=config.VERP_PERSONALIZED_DELIVERIES)
+
+
+
+def delete_member(mlist, address, admin_notif=None, userack=None):
+ if userack is None:
+ userack = mlist.send_goodbye_msg
+ if admin_notif is None:
+ admin_notif = mlist.admin_notify_mchanges
+ # Delete a member, for which we know the approval has been made
+ member = mlist.members.get_member(address)
+ language = member.preferred_language
+ member.unsubscribe()
+ # And send an acknowledgement to the user...
+ if userack:
+ send_goodbye_message(mlist, address, language)
+ # ...and to the administrator.
+ if admin_notif:
+ user = config.db.user_manager.get_user(address)
+ realname = user.real_name
+ subject = _('$mlist.real_name unsubscription notification')
+ text = Utils.maketext(
+ 'adminunsubscribeack.txt',
+ {'listname': mlist.real_name,
+ 'member' : formataddr((realname, address)),
+ }, mlist=mlist)
+ msg = Message.OwnerNotification(mlist, subject, text)
+ msg.send(mlist)
+
+
+
+def send_goodbye_message(mlist, address, language):
+ if mlist.goodbye_msg:
+ goodbye = Utils.wrap(mlist.goodbye_msg) + '\n'
+ else:
+ goodbye = ''
+ msg = Message.UserNotification(
+ address, mlist.bounces_address,
+ _('You have been unsubscribed from the $mlist.real_name mailing list'),
+ goodbye, language)
+ msg.send(mlist, verp=config.VERP_PERSONALIZED_DELIVERIES)
diff --git a/Mailman/app/moderator.py b/Mailman/app/moderator.py
index 54e8956f0..823832184 100644
--- a/Mailman/app/moderator.py
+++ b/Mailman/app/moderator.py
@@ -17,10 +17,13 @@
"""Application support for moderators."""
-from __future__ import with_statement
-
__all__ = [
+ 'handle_message',
+ 'handle_subscription',
+ 'handle_unsubscription',
'hold_message',
+ 'hold_subscription',
+ 'hold_unsubscription',
]
import logging
@@ -33,7 +36,7 @@ from Mailman import Message
from Mailman import Utils
from Mailman import i18n
from Mailman.Queue.sbcache import get_switchboard
-from Mailman.app.membership import add_member
+from Mailman.app.membership import add_member, delete_member
from Mailman.configuration import config
from Mailman.constants import Action, DeliveryMode
from Mailman.interfaces import RequestType
@@ -188,9 +191,8 @@ def hold_subscription(mlist, address, realname, password, mode, language):
mlist.fqdn_listname, address)
# Possibly notify the administrator in default list language
if mlist.admin_immed_notify:
- realname = mlist.real_name
subject = _(
- 'New subscription request to list $realname from $address')
+ 'New subscription request to list $mlist.real_name from $address')
text = Utils.maketext(
'subauth.txt',
{'username' : address,
@@ -241,54 +243,62 @@ def handle_subscription(mlist, id, action, comment=None):
raise AssertionError('Unexpected action: %s' % action)
# Delete the request from the database.
requestdb.delete_request(id)
- return
-def HoldUnsubscription(self, addr):
- # Assure the database is open for writing
- self._opendb()
- # Get the next unique id
- id = self._next_id
- # All we need to do is save the unsubscribing address
- self._db[id] = (UNSUBSCRIPTION, addr)
+def hold_unsubscription(mlist, address):
+ data = dict(address=address)
+ requestsdb = config.db.requests.get_list_requests(mlist)
+ request_id = requestsdb.hold_request(
+ RequestType.unsubscription, address, data)
vlog.info('%s: held unsubscription request from %s',
- self.internal_name(), addr)
+ mlist.fqdn_listname, address)
# Possibly notify the administrator of the hold
- if self.admin_immed_notify:
- realname = self.real_name
+ if mlist.admin_immed_notify:
subject = _(
- 'New unsubscription request from %(realname)s by %(addr)s')
+ 'New unsubscription request from $mlist.real_name by $address')
text = Utils.maketext(
'unsubauth.txt',
- {'username' : addr,
- 'listname' : self.internal_name(),
- 'hostname' : self.host_name,
- 'admindb_url': self.GetScriptURL('admindb', absolute=1),
- }, mlist=self)
+ {'address' : address,
+ 'listname' : mlist.fqdn_listname,
+ 'admindb_url': mlist.script_url('admindb'),
+ }, mlist=mlist)
# This message should appear to come from the <list>-owner so as
# to avoid any useless bounce processing.
- owneraddr = self.GetOwnerEmail()
- msg = Message.UserNotification(owneraddr, owneraddr, subject, text,
- self.preferred_language)
- msg.send(self, **{'tomoderators': 1})
+ msg = Message.UserNotification(
+ mlist.owner_address, mlist.owner_address,
+ subject, text, mlist.preferred_language)
+ msg.send(mlist, tomoderators=True)
+ return request_id
-def _handleunsubscription(self, record, value, comment):
- addr = record
- if value == config.DEFER:
- return DEFER
- elif value == config.DISCARD:
+
+
+def handle_unsubscription(mlist, id, action, comment=None):
+ requestdb = config.db.requests.get_list_requests(mlist)
+ key, data = requestdb.get_request(id)
+ address = data['address']
+ if action is Action.defer:
+ # Nothing to do.
+ return
+ elif action is Action.discard:
+ # Nothing to do except delete the request from the database.
pass
- elif value == config.REJECT:
- self._refuse(_('Unsubscription request'), addr, comment)
- else:
- assert value == config.UNSUBSCRIBE
+ elif action is Action.reject:
+ key, data = requestdb.get_request(id)
+ _refuse(mlist, _('Unsubscription request'), address,
+ comment or _('[No reason given]'))
+ elif action is Action.accept:
+ key, data = requestdb.get_request(id)
try:
- self.ApprovedDeleteMember(addr)
+ delete_member(mlist, address)
except Errors.NotAMemberError:
- # User has already been unsubscribed
+ # User has already been unsubscribed.
pass
- return REMOVE
+ slog.info('%s: deleted %s', mlist.fqdn_listname, address)
+ else:
+ raise AssertionError('Unexpected action: %s' % action)
+ # Delete the request from the database.
+ requestdb.delete_request(id)
@@ -324,41 +334,3 @@ def _refuse(mlist, request, recip, comment, origmsg=None, lang=None):
msg = Message.UserNotification(recip, mlist.bounces_address,
subject, text, lang)
msg.send(mlist)
-
-
-
-def readMessage(path):
- # For backwards compatibility, we must be able to read either a flat text
- # file or a pickle.
- ext = os.path.splitext(path)[1]
- with open(path) as fp:
- if ext == '.txt':
- msg = email.message_from_file(fp, Message.Message)
- else:
- assert ext == '.pck'
- msg = cPickle.load(fp)
- return msg
-
-
-
-def handle_request(mlist, id, value,
- comment=None, preserve=None, forward=None, addr=None):
- requestsdb = config.db.get_list_requests(mlist)
- key, data = requestsdb.get_record(id)
-
- self._opendb()
- rtype, data = self._db[id]
- if rtype == HELDMSG:
- status = self._handlepost(data, value, comment, preserve,
- forward, addr)
- elif rtype == UNSUBSCRIPTION:
- status = self._handleunsubscription(data, value, comment)
- else:
- assert rtype == SUBSCRIPTION
- status = self._handlesubscription(data, value, comment)
- if status <> DEFER:
- # BAW: Held message ids are linked to Pending cookies, allowing
- # the user to cancel their post before the moderator has approved
- # it. We should probably remove the cookie associated with this
- # id, but we have no way currently of correlating them. :(
- del self._db[id]
diff --git a/Mailman/docs/requests.txt b/Mailman/docs/requests.txt
index 05be06aac..242d57b9c 100644
--- a/Mailman/docs/requests.txt
+++ b/Mailman/docs/requests.txt
@@ -548,8 +548,8 @@ subscriber.
('recips', ['cperson@example.org']),
('reduced_list_headers', True), ('version', 3)]
-Or the subscription can be approved/accepted. This subscribes the member to
-the mailing list.
+The subscription can also be accepted. This subscribes the address to the
+mailing list.
>>> mlist.send_welcome_msg = True
>>> id_4 = moderator.hold_subscription(mlist,
@@ -711,28 +711,189 @@ unsubscription holds can send the list's moderators an immediate notification.
>>> mlist.admin_immed_notify = False
>>> flush()
>>> from Mailman.constants import MemberRole
- >>> user_1 = config.db.user_manager.create_user('dperson@example.com')
+ >>> user_1 = config.db.user_manager.create_user('gperson@example.com')
>>> flush()
>>> address_1 = list(user_1.addresses)[0]
>>> address_1.subscribe(mlist, MemberRole.member)
- <Member: dperson@example.com on alist@example.com as MemberRole.member>
- >>> user_2 = config.db.user_manager.create_user('eperson@example.com')
+ <Member: gperson@example.com on alist@example.com as MemberRole.member>
+ >>> user_2 = config.db.user_manager.create_user('hperson@example.com')
>>> flush()
>>> address_2 = list(user_2.addresses)[0]
>>> address_2.subscribe(mlist, MemberRole.member)
- <Member: eperson@example.com on alist@example.com as MemberRole.member>
+ <Member: hperson@example.com on alist@example.com as MemberRole.member>
>>> flush()
- >>> id_5 = moderator.hold_unsubscription(mlist, 'dperson@example.com')
+ >>> id_5 = moderator.hold_unsubscription(mlist, 'gperson@example.com')
>>> flush()
- >>> requests.get_request(id_5) is not None)
+ >>> requests.get_request(id_5) is not None
True
>>> virginq.files
[]
>>> mlist.admin_immed_notify = True
- >>> id_6 = moderator.hold_unsubscription(mlist, 'eperson@example.com')
+ >>> id_6 = moderator.hold_unsubscription(mlist, 'hperson@example.com')
>>> flush()
>>> qmsg, qdata = dequeue()
>>> print qmsg.as_string()
- XXX
+ MIME-Version: 1.0
+ Content-Type: text/plain; charset="us-ascii"
+ Content-Transfer-Encoding: 7bit
+ Subject: New unsubscription request from A Test List by hperson@example.com
+ From: alist-owner@example.com
+ To: alist-owner@example.com
+ Message-ID: ...
+ Date: ...
+ Precedence: bulk
+ <BLANKLINE>
+ Your authorization is required for a mailing list unsubscription
+ request approval:
+ <BLANKLINE>
+ By: hperson@example.com
+ From: alist@example.com
+ <BLANKLINE>
+ At your convenience, visit:
+ <BLANKLINE>
+ http://www.example.com/admindb/alist@example.com
+ <BLANKLINE>
+ to process the request.
+ <BLANKLINE>
>>> sorted(qdata.items())
- XXX
+ [('_parsemsg', False),
+ ('listname', 'alist@example.com'), ('nodecorate', True),
+ ('received_time', ...),
+ ('recips', ['alist-owner@example.com']),
+ ('reduced_list_headers', True), ('tomoderators', True), ('version', 3)]
+
+There are now two addresses with held unsubscription requests. As above, one
+of the actions we can take is to defer to the decision.
+
+ >>> moderator.handle_unsubscription(mlist, id_5, Action.defer)
+ >>> flush()
+ >>> requests.get_request(id_5) is not None
+ True
+
+The held unsubscription can also be discarded, and the member will remain
+subscribed.
+
+ >>> moderator.handle_unsubscription(mlist, id_5, Action.discard)
+ >>> flush()
+ >>> print requests.get_request(id_5)
+ None
+ >>> mlist.members.get_member('gperson@example.com')
+ <Member: gperson@example.com on alist@example.com as MemberRole.member>
+
+The request can be rejected, in which case a message is sent to the member,
+and the person remains a member of the mailing list.
+
+ >>> moderator.handle_unsubscription(mlist, id_6, Action.reject,
+ ... 'This list is a prison.')
+ >>> flush()
+ >>> print requests.get_request(id_6)
+ None
+ >>> qmsg, qdata = dequeue()
+ >>> print qmsg.as_string()
+ MIME-Version: 1.0
+ Content-Type: text/plain; charset="us-ascii"
+ Content-Transfer-Encoding: 7bit
+ Subject: Request to mailing list "A Test List" rejected
+ From: alist-bounces@example.com
+ To: hperson@example.com
+ Message-ID: ...
+ Date: ...
+ Precedence: bulk
+ <BLANKLINE>
+ Your request to the alist@example.com mailing list
+ <BLANKLINE>
+ Unsubscription request
+ <BLANKLINE>
+ has been rejected by the list moderator. The moderator gave the
+ following reason for rejecting your request:
+ <BLANKLINE>
+ "This list is a prison."
+ <BLANKLINE>
+ Any questions or comments should be directed to the list administrator
+ at:
+ <BLANKLINE>
+ alist-owner@example.com
+ <BLANKLINE>
+ >>> sorted(qdata.items())
+ [('_parsemsg', False),
+ ('listname', 'alist@example.com'),
+ ('nodecorate', True), ('received_time', ...),
+ ('recips', ['hperson@example.com']),
+ ('reduced_list_headers', True), ('version', 3)]
+ >>> mlist.members.get_member('hperson@example.com')
+ <Member: hperson@example.com on alist@example.com as MemberRole.member>
+
+The unsubscription request can also be accepted. This removes the member from
+the mailing list.
+
+ >>> mlist.send_goodbye_msg = True
+ >>> mlist.goodbye_msg = 'So long!'
+ >>> mlist.admin_immed_notify = False
+ >>> flush()
+ >>> id_7 = moderator.hold_unsubscription(mlist, 'gperson@example.com')
+ >>> flush()
+ >>> moderator.handle_unsubscription(mlist, id_7, Action.accept)
+ >>> flush()
+ >>> print mlist.members.get_member('gperson@example.com')
+ None
+
+There are now two messages in the virgin queue, one to the member who was just
+unsubscribed and another to the moderators informing them of this membership
+change.
+
+ >>> qmsg_1, qdata_1 = dequeue(expected_count=2)
+ >>> qmsg_2, qdata_2 = dequeue()
+ >>> if 'gperson@example.com' in qdata_1['recips']:
+ ... # The first message is the goodbye message
+ ... goodbye_qmsg = qmsg_1
+ ... goodbye_qdata = qdata_1
+ ... admin_qmsg = qmsg_2
+ ... admin_qdata = qdata_2
+ ... else:
+ ... goodbye_qmsg = qmsg_2
+ ... goodbye_qdata = qdata_2
+ ... admin_qmsg = qmsg_1
+ ... admin_qdata = qdata_1
+
+The goodbye message...
+
+ >>> print goodbye_qmsg.as_string()
+ MIME-Version: 1.0
+ Content-Type: text/plain; charset="us-ascii"
+ Content-Transfer-Encoding: 7bit
+ Subject: You have been unsubscribed from the A Test List mailing list
+ From: alist-bounces@example.com
+ To: gperson@example.com
+ Message-ID: ...
+ Date: ...
+ Precedence: bulk
+ <BLANKLINE>
+ So long!
+ <BLANKLINE>
+ >>> sorted(goodbye_qdata.items())
+ [('_parsemsg', False),
+ ('listname', 'alist@example.com'),
+ ('nodecorate', True), ('received_time', ...),
+ ('recips', ['gperson@example.com']),
+ ('reduced_list_headers', True), ('verp', False), ('version', 3)]
+
+...and the admin message.
+
+ >>> print admin_qmsg.as_string()
+ MIME-Version: 1.0
+ Content-Type: text/plain; charset="us-ascii"
+ Content-Transfer-Encoding: 7bit
+ Subject: A Test List unsubscription notification
+ From: changeme@example.com
+ To: alist-owner@example.com
+ Message-ID: ...
+ Date: ...
+ Precedence: bulk
+ <BLANKLINE>
+ gperson@example.com has been removed from A Test List.
+ <BLANKLINE>
+ >>> sorted(admin_qdata.items())
+ [('_parsemsg', False), ('envsender', 'changeme@example.com'),
+ ('listname', 'alist@example.com'),
+ ('nodecorate', True), ('received_time', ...),
+ ('recips', []), ('reduced_list_headers', True), ('version', 3)]
diff --git a/Mailman/templates/en/adminunsubscribeack.txt b/Mailman/templates/en/adminunsubscribeack.txt
index 2ebcfeb70..fb87ac87d 100644
--- a/Mailman/templates/en/adminunsubscribeack.txt
+++ b/Mailman/templates/en/adminunsubscribeack.txt
@@ -1,2 +1 @@
%(member)s has been removed from %(listname)s.
-
diff --git a/Mailman/templates/en/unsubauth.txt b/Mailman/templates/en/unsubauth.txt
index 920f6c1b6..40bc3d5e6 100644
--- a/Mailman/templates/en/unsubauth.txt
+++ b/Mailman/templates/en/unsubauth.txt
@@ -1,8 +1,8 @@
Your authorization is required for a mailing list unsubscription
request approval:
- By: %(username)s
- From: %(listname)s@%(hostname)s
+ By: %(address)s
+ From: %(listname)s
At your convenience, visit: