------------------------------------------------------------
revno: 6551
committer: Barry Warsaw <[EMAIL PROTECTED]>
branch nick: 3.0
timestamp: Sun 2007-09-16 12:08:24 -0400
message:
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.
modified:
Mailman/Deliverer.py
Mailman/MailList.py
Mailman/app/membership.py
Mailman/app/moderator.py
Mailman/docs/requests.txt
Mailman/templates/en/adminunsubscribeack.txt
Mailman/templates/en/unsubauth.txt
=== modified file 'Mailman/Deliverer.py'
--- a/Mailman/Deliverer.py 2007-08-01 20:11:08 +0000
+++ b/Mailman/Deliverer.py 2007-09-16 16:08:24 +0000
@@ -37,53 +37,6 @@
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()
=== modified file 'Mailman/MailList.py'
--- a/Mailman/MailList.py 2007-09-16 11:15:53 +0000
+++ b/Mailman/MailList.py 2007-09-16 16:08:24 +0000
@@ -597,37 +597,6 @@
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:
=== modified file 'Mailman/app/membership.py'
--- a/Mailman/app/membership.py 2007-09-16 11:15:53 +0000
+++ b/Mailman/app/membership.py 2007-09-16 16:08:24 +0000
@@ -151,3 +151,43 @@
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)
=== modified file 'Mailman/app/moderator.py'
--- a/Mailman/app/moderator.py 2007-09-16 11:15:53 +0000
+++ b/Mailman/app/moderator.py 2007-09-16 16:08:24 +0000
@@ -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 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 @@
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 @@
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})
-
-def _handleunsubscription(self, record, value, comment):
- addr = record
- if value == config.DEFER:
- return DEFER
- elif value == config.DISCARD:
+ msg = Message.UserNotification(
+ mlist.owner_address, mlist.owner_address,
+ subject, text, mlist.preferred_language)
+ msg.send(mlist, tomoderators=True)
+ return request_id
+
+
+
+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 @@
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]
=== modified file 'Mailman/docs/requests.txt'
--- a/Mailman/docs/requests.txt 2007-09-16 11:15:53 +0000
+++ b/Mailman/docs/requests.txt 2007-09-16 16:08:24 +0000
@@ -548,8 +548,8 @@
('recips', ['[EMAIL PROTECTED]']),
('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 @@
>>> mlist.admin_immed_notify = False
>>> flush()
>>> from Mailman.constants import MemberRole
- >>> user_1 = config.db.user_manager.create_user('[EMAIL PROTECTED]')
+ >>> user_1 = config.db.user_manager.create_user('[EMAIL PROTECTED]')
>>> flush()
>>> address_1 = list(user_1.addresses)[0]
>>> address_1.subscribe(mlist, MemberRole.member)
- <Member: [EMAIL PROTECTED] on [EMAIL PROTECTED] as MemberRole.member>
- >>> user_2 = config.db.user_manager.create_user('[EMAIL PROTECTED]')
+ <Member: [EMAIL PROTECTED] on [EMAIL PROTECTED] as MemberRole.member>
+ >>> user_2 = config.db.user_manager.create_user('[EMAIL PROTECTED]')
>>> flush()
>>> address_2 = list(user_2.addresses)[0]
>>> address_2.subscribe(mlist, MemberRole.member)
- <Member: [EMAIL PROTECTED] on [EMAIL PROTECTED] as MemberRole.member>
- >>> flush()
- >>> id_5 = moderator.hold_unsubscription(mlist, '[EMAIL PROTECTED]')
- >>> flush()
- >>> requests.get_request(id_5) is not None)
+ <Member: [EMAIL PROTECTED] on [EMAIL PROTECTED] as MemberRole.member>
+ >>> flush()
+ >>> id_5 = moderator.hold_unsubscription(mlist, '[EMAIL PROTECTED]')
+ >>> flush()
+ >>> requests.get_request(id_5) is not None
True
>>> virginq.files
[]
>>> mlist.admin_immed_notify = True
- >>> id_6 = moderator.hold_unsubscription(mlist, '[EMAIL PROTECTED]')
- >>> flush()
- >>> qmsg, qdata = dequeue()
- >>> print qmsg.as_string()
- XXX
- >>> sorted(qdata.items())
- XXX
+ >>> id_6 = moderator.hold_unsubscription(mlist, '[EMAIL PROTECTED]')
+ >>> flush()
+ >>> qmsg, qdata = dequeue()
+ >>> print qmsg.as_string()
+ MIME-Version: 1.0
+ Content-Type: text/plain; charset="us-ascii"
+ Content-Transfer-Encoding: 7bit
+ Subject: New unsubscription request from A Test List by [EMAIL PROTECTED]
+ From: [EMAIL PROTECTED]
+ To: [EMAIL PROTECTED]
+ Message-ID: ...
+ Date: ...
+ Precedence: bulk
+ <BLANKLINE>
+ Your authorization is required for a mailing list unsubscription
+ request approval:
+ <BLANKLINE>
+ By: [EMAIL PROTECTED]
+ From: [EMAIL PROTECTED]
+ <BLANKLINE>
+ At your convenience, visit:
+ <BLANKLINE>
+ http://www.example.com/admindb/[EMAIL PROTECTED]
+ <BLANKLINE>
+ to process the request.
+ <BLANKLINE>
+ >>> sorted(qdata.items())
+ [('_parsemsg', False),
+ ('listname', '[EMAIL PROTECTED]'), ('nodecorate', True),
+ ('received_time', ...),
+ ('recips', ['[EMAIL PROTECTED]']),
+ ('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('[EMAIL PROTECTED]')
+ <Member: [EMAIL PROTECTED] on [EMAIL PROTECTED] 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: [EMAIL PROTECTED]
+ To: [EMAIL PROTECTED]
+ Message-ID: ...
+ Date: ...
+ Precedence: bulk
+ <BLANKLINE>
+ Your request to the [EMAIL PROTECTED] 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>
+ [EMAIL PROTECTED]
+ <BLANKLINE>
+ >>> sorted(qdata.items())
+ [('_parsemsg', False),
+ ('listname', '[EMAIL PROTECTED]'),
+ ('nodecorate', True), ('received_time', ...),
+ ('recips', ['[EMAIL PROTECTED]']),
+ ('reduced_list_headers', True), ('version', 3)]
+ >>> mlist.members.get_member('[EMAIL PROTECTED]')
+ <Member: [EMAIL PROTECTED] on [EMAIL PROTECTED] 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, '[EMAIL PROTECTED]')
+ >>> flush()
+ >>> moderator.handle_unsubscription(mlist, id_7, Action.accept)
+ >>> flush()
+ >>> print mlist.members.get_member('[EMAIL PROTECTED]')
+ 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 '[EMAIL PROTECTED]' 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: [EMAIL PROTECTED]
+ To: [EMAIL PROTECTED]
+ Message-ID: ...
+ Date: ...
+ Precedence: bulk
+ <BLANKLINE>
+ So long!
+ <BLANKLINE>
+ >>> sorted(goodbye_qdata.items())
+ [('_parsemsg', False),
+ ('listname', '[EMAIL PROTECTED]'),
+ ('nodecorate', True), ('received_time', ...),
+ ('recips', ['[EMAIL PROTECTED]']),
+ ('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: [EMAIL PROTECTED]
+ To: [EMAIL PROTECTED]
+ Message-ID: ...
+ Date: ...
+ Precedence: bulk
+ <BLANKLINE>
+ [EMAIL PROTECTED] has been removed from A Test List.
+ <BLANKLINE>
+ >>> sorted(admin_qdata.items())
+ [('_parsemsg', False), ('envsender', '[EMAIL PROTECTED]'),
+ ('listname', '[EMAIL PROTECTED]'),
+ ('nodecorate', True), ('received_time', ...),
+ ('recips', []), ('reduced_list_headers', True), ('version', 3)]
=== modified file 'Mailman/templates/en/adminunsubscribeack.txt'
--- a/Mailman/templates/en/adminunsubscribeack.txt 2001-05-18 21:28:54
+0000
+++ b/Mailman/templates/en/adminunsubscribeack.txt 2007-09-16 16:08:24
+0000
@@ -1,2 +1,1 @@
%(member)s has been removed from %(listname)s.
-
=== modified file 'Mailman/templates/en/unsubauth.txt'
--- a/Mailman/templates/en/unsubauth.txt 2001-10-21 06:41:57 +0000
+++ b/Mailman/templates/en/unsubauth.txt 2007-09-16 16:08:24 +0000
@@ -1,8 +1,8 @@
Your authorization is required for a mailing list unsubscription
request approval:
- By: %(username)s
- From: %(listname)[EMAIL PROTECTED](hostname)s
+ By: %(address)s
+ From: %(listname)s
At your convenience, visit:
--
https://code.launchpad.net/~mailman-coders/mailman/3.0
You are receiving this branch notification because you are subscribed to it.
To unsubscribe from this branch go to
https://code.launchpad.net/~mailman-coders/mailman/3.0/+subscription/mailman-checkins.
_______________________________________________
Mailman-checkins mailing list
[email protected]
Unsubscribe:
http://mail.python.org/mailman/options/mailman-checkins/archive%40jab.org