Commit 7174e2fc authored by Barry Warsaw's avatar Barry Warsaw

* Leave a mailing list via the REST API.

* delete_member(): If the address is not associated with a member, raise
  NotAMemberError.
* NotAMemberError -> interfaces/member.py
parent f422ddd4
......@@ -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...
......
......@@ -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)
......
......@@ -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()
......
......@@ -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
......
......@@ -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."""
......
......@@ -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.
"""
......@@ -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 ''
......@@ -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'[email protected]'])
>>> 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/[email protected]/member/[email protected]
...
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': '[email protected]',
... 'address': '[email protected]',
... })
Elly is no longer a member of the mailing list.
>>> set(member.mailing_list for member in elly.memberships.members)
set([])
......@@ -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
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment