summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/mailman/app/membership.py6
-rw-r--r--src/mailman/app/moderator.py6
-rw-r--r--src/mailman/bin/disabled.py6
-rw-r--r--src/mailman/core/errors.py1
-rw-r--r--src/mailman/interfaces/member.py15
-rw-r--r--src/mailman/interfaces/membership.py20
-rw-r--r--src/mailman/rest/adapters.py13
-rw-r--r--src/mailman/rest/docs/membership.txt35
-rw-r--r--src/mailman/tests/test_membership.py2
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