diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/mailman/app/membership.py | 6 | ||||
| -rw-r--r-- | src/mailman/app/moderator.py | 6 | ||||
| -rw-r--r-- | src/mailman/bin/disabled.py | 6 | ||||
| -rw-r--r-- | src/mailman/core/errors.py | 1 | ||||
| -rw-r--r-- | src/mailman/interfaces/member.py | 15 | ||||
| -rw-r--r-- | src/mailman/interfaces/membership.py | 20 | ||||
| -rw-r--r-- | src/mailman/rest/adapters.py | 13 | ||||
| -rw-r--r-- | src/mailman/rest/docs/membership.txt | 35 | ||||
| -rw-r--r-- | src/mailman/tests/test_membership.py | 2 |
9 files changed, 89 insertions, 15 deletions
diff --git a/src/mailman/app/membership.py b/src/mailman/app/membership.py index 2adba814a..09ecf3a8b 100644 --- a/src/mailman/app/membership.py +++ b/src/mailman/app/membership.py @@ -132,13 +132,17 @@ def delete_member(mlist, address, admin_notif=None, userack=None): this member was deleted. :type admin_notif: bool, or None to let the mailing list's `admin_notify_mchange` attribute decide. + :raises NotAMemberError: if the address is not a member of the + mailing list. """ 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 + # Delete a member, for which we know the approval has been made. member = mlist.members.get_member(address) + if member is None: + raise NotAMemberError(mlist, address) language = member.preferred_language member.unsubscribe() # And send an acknowledgement to the user... diff --git a/src/mailman/app/moderator.py b/src/mailman/app/moderator.py index e29213f3f..0456622cf 100644 --- a/src/mailman/app/moderator.py +++ b/src/mailman/app/moderator.py @@ -40,12 +40,12 @@ from mailman.app.membership import add_member, delete_member from mailman.app.notifications import ( send_admin_subscription_notice, send_welcome_message) from mailman.config import config -from mailman.core import errors from mailman.core.i18n import _ from mailman.email.message import UserNotification from mailman.interfaces.action import Action from mailman.interfaces.languages import ILanguageManager -from mailman.interfaces.member import AlreadySubscribedError, DeliveryMode +from mailman.interfaces.member import ( + AlreadySubscribedError, DeliveryMode, NotAMemberError) from mailman.interfaces.messages import IMessageStore from mailman.interfaces.requests import IRequests, RequestType @@ -315,7 +315,7 @@ def handle_unsubscription(mlist, id, action, comment=None): key, data = requestdb.get_request(id) try: delete_member(mlist, address) - except errors.NotAMemberError: + except NotAMemberError: # User has already been unsubscribed. pass slog.info('%s: deleted %s', mlist.fqdn_listname, address) diff --git a/src/mailman/bin/disabled.py b/src/mailman/bin/disabled.py index 1ff59ac5e..e444f8dfc 100644 --- a/src/mailman/bin/disabled.py +++ b/src/mailman/bin/disabled.py @@ -19,7 +19,6 @@ import time import logging import optparse -from mailman import errors from mailman import MailList from mailman import MemberAdaptor from mailman import Pending @@ -27,6 +26,7 @@ from mailman import loginit from mailman.Bouncer import _BounceInfo from mailman.configuration import config from mailman.core.i18n import _ +from mailman.interfaces.member import NotAMemberError from mailman.version import MAILMAN_VERSION @@ -183,12 +183,12 @@ def main(): member, mlist.internal_name()) try: mlist.sendNextNotification(member) - except errors.NotAMemberError: + except NotAMemberError: # There must have been some problem with the data we have # on this member. Most likely it's that they don't have a # password assigned. Log this and delete the member. blog.info( - 'NotAMemberError when sending disabled notice: %s', + 'Cannot send disable notice to non-member: %s', member) mlist.ApprovedDeleteMember(member, 'cron/disabled') mlist.Save() diff --git a/src/mailman/core/errors.py b/src/mailman/core/errors.py index 2d31863c3..7dbc851d1 100644 --- a/src/mailman/core/errors.py +++ b/src/mailman/core/errors.py @@ -58,7 +58,6 @@ class MailmanException(Exception): # "New" style membership exceptions (new w/ MM2.1) class MemberError(MailmanException): pass -class NotAMemberError(MemberError): pass class AlreadyReceivingDigests(MemberError): pass class AlreadyReceivingRegularDeliveries(MemberError): pass class CantDigestError(MemberError): pass diff --git a/src/mailman/interfaces/member.py b/src/mailman/interfaces/member.py index 87ad19c83..66cf61581 100644 --- a/src/mailman/interfaces/member.py +++ b/src/mailman/interfaces/member.py @@ -28,6 +28,7 @@ __all__ = [ 'MemberRole', 'MembershipError', 'MembershipIsBannedError', + 'NotAMemberError', ] @@ -105,6 +106,20 @@ class MembershipIsBannedError(MembershipError): self._address, self._mlist) +@error_status(400) +class NotAMemberError(MembershipError): + """The address is not a member of the mailing list.""" + + def __init__(self, mlist, address): + super(NotAMemberError, self).__init__() + self._mlist = mlist + self._address = address + + def __str__(self): + return '{0} is not a member of {1.fqdn_listname}'.format( + self._address, self._mlist) + + class IMember(Interface): """A member of a mailing list.""" diff --git a/src/mailman/interfaces/membership.py b/src/mailman/interfaces/membership.py index 51e36c6e5..3e8e6d4b5 100644 --- a/src/mailman/interfaces/membership.py +++ b/src/mailman/interfaces/membership.py @@ -93,3 +93,23 @@ class ISubscriptionService(Interface): :raises NoSuchListError: if the named mailing list does not exist. :raises ValueError: when `delivery_mode` is invalid. """ + + @operation_parameters( + fqdn_listname=TextLine(), + address=TextLine(), + ) + @export_write_operation() + def leave(fqdn_listname, address): + """Unsubscribe from a mailing list. + + :param fqdn_listname: The posting address of the mailing list to + subscribe the user to. + :type fqdn_listname: string + :param address: The address of the user getting subscribed. + :type address: string + :raises InvalidEmailAddressError: if the email address is not valid. + :raises NoSuchListError: if the named mailing list does not exist. + :raises NotAMemberError: if the given address is not a member of the + mailing list. + """ + diff --git a/src/mailman/rest/adapters.py b/src/mailman/rest/adapters.py index 0cc5e2f95..ab0279d6e 100644 --- a/src/mailman/rest/adapters.py +++ b/src/mailman/rest/adapters.py @@ -31,12 +31,12 @@ from zope.component import getUtility from zope.interface import implements from zope.publisher.interfaces import NotFound -from mailman.app.membership import add_member +from mailman.app.membership import add_member, delete_member from mailman.core.constants import system_preferences from mailman.interfaces.address import InvalidEmailAddressError from mailman.interfaces.domain import IDomainCollection, IDomainManager from mailman.interfaces.listmanager import IListManager, NoSuchListError -from mailman.interfaces.member import DeliveryMode +from mailman.interfaces.member import DeliveryMode, NotAMemberError from mailman.interfaces.membership import ISubscriptionService from mailman.interfaces.rest import IResolvePathNames @@ -125,3 +125,12 @@ class SubscriptionService: # new to us. return add_member(mlist, address, real_name, None, mode, system_preferences.preferred_language) + + def leave(self, fqdn_listname, address): + """See `ISubscriptionService`.""" + mlist = getUtility(IListManager).get(fqdn_listname) + if mlist is None: + raise NoSuchListError(fqdn_listname) + # XXX for now, no notification or user acknowledgement. + delete_member(mlist, address, False, False) + return '' diff --git a/src/mailman/rest/docs/membership.txt b/src/mailman/rest/docs/membership.txt index 0e333f3d1..42c6bb242 100644 --- a/src/mailman/rest/docs/membership.txt +++ b/src/mailman/rest/docs/membership.txt @@ -136,7 +136,7 @@ test-one mailing list. >>> subscribe(mlist_one, 'Cris', MemberRole.owner) >>> subscribe(mlist_two, 'Dave', MemberRole.moderator) - + >>> dump_json('http://localhost:8001/3.0/members') entry 0: http_etag: ... @@ -198,7 +198,34 @@ Elly is now a member of the mailing list. >>> elly <User "Elly Person" at ...> - >>> member_of_lists = set(member.mailing_list - ... for member in elly.memberships.members) - >>> member_of_lists + >>> set(member.mailing_list for member in elly.memberships.members) set([u'alpha@example.com']) + + >>> dump_json('http://localhost:8001/3.0/members') + entry 0: + ... + entry 3: + http_etag: ... + resource_type_link: http://localhost:8001/3.0/#member + self_link: http://localhost:8001/3.0/lists/alpha@example.com/member/eperson@example.com + ... + + +Leaving a mailing list +====================== + +Elly decides she does not want to be a member of the mailing list after all, +so she unsubscribes from the mailing list. + + # Ensure our previous reads don't keep the database lock. + >>> transaction.abort() + >>> dump_json('http://localhost:8001/3.0/members', { + ... 'ws.op': 'leave', + ... 'fqdn_listname': 'alpha@example.com', + ... 'address': 'eperson@example.com', + ... }) + +Elly is no longer a member of the mailing list. + + >>> set(member.mailing_list for member in elly.memberships.members) + set([]) diff --git a/src/mailman/tests/test_membership.py b/src/mailman/tests/test_membership.py index 9fa00867d..de8d0b29c 100644 --- a/src/mailman/tests/test_membership.py +++ b/src/mailman/tests/test_membership.py @@ -30,7 +30,7 @@ import unittest from mailman import passwords from mailman.config import config -from mailman.core.errors import NotAMemberError +from mailman.interfaces.member import NotAMemberError |
