diff options
| author | Barry Warsaw | 2007-09-16 12:08:24 -0400 |
|---|---|---|
| committer | Barry Warsaw | 2007-09-16 12:08:24 -0400 |
| commit | 0d623d7684e2ad8309219da934fc956a498e3a71 (patch) | |
| tree | 8e0e5430aae3b02078cf1a921905c420623980d7 /Mailman | |
| parent | 0a3e802568a0bfaf9ceade4614a52db9a90b905c (diff) | |
| download | mailman-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.
Diffstat (limited to 'Mailman')
| -rw-r--r-- | Mailman/Deliverer.py | 47 | ||||
| -rw-r--r-- | Mailman/MailList.py | 31 | ||||
| -rw-r--r-- | Mailman/app/membership.py | 40 | ||||
| -rw-r--r-- | Mailman/app/moderator.py | 124 | ||||
| -rw-r--r-- | Mailman/docs/requests.txt | 183 | ||||
| -rw-r--r-- | Mailman/templates/en/adminunsubscribeack.txt | 1 | ||||
| -rw-r--r-- | Mailman/templates/en/unsubauth.txt | 4 |
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: |
