summaryrefslogtreecommitdiff
path: root/src/mailman/app
diff options
context:
space:
mode:
authorBarry Warsaw2012-09-04 21:31:50 -0400
committerBarry Warsaw2012-09-04 21:31:50 -0400
commit3c77c83ec4070e7183482445ff0b9a30398b72f4 (patch)
tree7d992c11114cc2e8cb1c22d65f517c1d1f5f201f /src/mailman/app
parent56c3bf127b5fb912461e638b6936c627d30be744 (diff)
downloadmailman-3c77c83ec4070e7183482445ff0b9a30398b72f4.tar.gz
mailman-3c77c83ec4070e7183482445ff0b9a30398b72f4.tar.zst
mailman-3c77c83ec4070e7183482445ff0b9a30398b72f4.zip
* The link between members and the mailing lists they are subscribed to, is
now via the RFC 2369 `list_id` instead of the fqdn listname (i.e. posting address). This is because while the posting address can change if the mailing list is moved to a new server, the list id is fixed. (LP: #1024509) + IListManager.get_by_list_id() added. + IListManager.list_ids added. + IMailingList.list_id added. + Several internal APIs that accepted fqdn list names now require list ids, e.g. ISubscriptionService.join() and .find_members(). + IMember.list_id attribute added; .mailing_list is now an alias that retrieves and returns the IMailingList. - list_id added (LP: #1024509)
Diffstat (limited to 'src/mailman/app')
-rw-r--r--src/mailman/app/bounces.py3
-rw-r--r--src/mailman/app/docs/lifecycle.rst2
-rw-r--r--src/mailman/app/docs/subscriptions.rst24
-rw-r--r--src/mailman/app/events.py2
-rw-r--r--src/mailman/app/lifecycle.py22
-rw-r--r--src/mailman/app/subscriptions.py53
-rw-r--r--src/mailman/app/tests/test_membership.py8
-rw-r--r--src/mailman/app/tests/test_subscriptions.py4
8 files changed, 56 insertions, 62 deletions
diff --git a/src/mailman/app/bounces.py b/src/mailman/app/bounces.py
index 0a291d671..41a8f3560 100644
--- a/src/mailman/app/bounces.py
+++ b/src/mailman/app/bounces.py
@@ -192,7 +192,8 @@ def send_probe(member, msg):
:return: The token representing this probe in the pendings database.
:rtype: string
"""
- mlist = getUtility(IListManager).get(member.mailing_list)
+ mlist = getUtility(IListManager).get_by_list_id(
+ member.mailing_list.list_id)
text = make('probe.txt', mlist, member.preferred_language.code,
listname=mlist.fqdn_listname,
address= member.address.email,
diff --git a/src/mailman/app/docs/lifecycle.rst b/src/mailman/app/docs/lifecycle.rst
index 9a3337123..f6bb7ddae 100644
--- a/src/mailman/app/docs/lifecycle.rst
+++ b/src/mailman/app/docs/lifecycle.rst
@@ -140,7 +140,7 @@ artifacts.
::
>>> from mailman.app.lifecycle import remove_list
- >>> remove_list(mlist_2.fqdn_listname, mlist_2)
+ >>> remove_list(mlist_2)
>>> from mailman.interfaces.listmanager import IListManager
>>> from zope.component import getUtility
diff --git a/src/mailman/app/docs/subscriptions.rst b/src/mailman/app/docs/subscriptions.rst
index f897d219e..dd8298cb3 100644
--- a/src/mailman/app/docs/subscriptions.rst
+++ b/src/mailman/app/docs/subscriptions.rst
@@ -30,13 +30,13 @@ role. At a minimum, a mailing list and an address for the new user is
required.
>>> mlist = create_list('test@example.com')
- >>> anne = service.join('test@example.com', 'anne@example.com')
+ >>> anne = service.join('test.example.com', 'anne@example.com')
>>> anne
<Member: anne <anne@example.com> on test@example.com as MemberRole.member>
The real name of the new member can be given.
- >>> bart = service.join('test@example.com', 'bart@example.com',
+ >>> bart = service.join('test.example.com', 'bart@example.com',
... 'Bart Person')
>>> bart
<Member: Bart Person <bart@example.com>
@@ -45,7 +45,7 @@ The real name of the new member can be given.
Other roles can also be subscribed.
>>> from mailman.interfaces.member import MemberRole
- >>> anne_owner = service.join('test@example.com', 'anne@example.com',
+ >>> anne_owner = service.join('test.example.com', 'anne@example.com',
... role=MemberRole.owner)
>>> anne_owner
<Member: anne <anne@example.com> on test@example.com as MemberRole.owner>
@@ -67,7 +67,7 @@ New members can also be added by providing an existing user id instead of an
email address. However, the user must have a preferred email address.
::
- >>> service.join('test@example.com', bart.user.user_id,
+ >>> service.join('test.example.com', bart.user.user_id,
... role=MemberRole.owner)
Traceback (most recent call last):
...
@@ -78,7 +78,7 @@ email address. However, the user must have a preferred email address.
>>> address = list(bart.user.addresses)[0]
>>> address.verified_on = now()
>>> bart.user.preferred_address = address
- >>> service.join('test@example.com', bart.user.user_id,
+ >>> service.join('test.example.com', bart.user.user_id,
... role=MemberRole.owner)
<Member: Bart Person <bart@example.com>
on test@example.com as MemberRole.owner>
@@ -89,7 +89,7 @@ Removing members
Regular members can also be removed.
- >>> cris = service.join('test@example.com', 'cris@example.com')
+ >>> cris = service.join('test.example.com', 'cris@example.com')
>>> service.get_members()
[<Member: anne <anne@example.com> on test@example.com
as MemberRole.owner>,
@@ -103,7 +103,7 @@ Regular members can also be removed.
as MemberRole.member>]
>>> sum(1 for member in service)
5
- >>> service.leave('test@example.com', 'cris@example.com')
+ >>> service.leave('test.example.com', 'cris@example.com')
>>> service.get_members()
[<Member: anne <anne@example.com> on test@example.com
as MemberRole.owner>,
@@ -173,7 +173,7 @@ Memberships can also be searched for by user id.
You can find all the memberships for a specific mailing list.
- >>> service.find_members(fqdn_listname='test@example.com')
+ >>> service.find_members(list_id='test.example.com')
[<Member: anne <anne@example.com> on test@example.com
as MemberRole.member>,
<Member: anne <anne@example.com> on test@example.com as MemberRole.owner>,
@@ -184,9 +184,11 @@ You can find all the memberships for a specific mailing list.
<Member: Bart Person <bart@example.com> on test@example.com
as MemberRole.owner>]
-You can find all the memberships for an address on a specific mailing list.
+You can find all the memberships for an address on a specific mailing list,
+but you have to give it the list id, not the fqdn listname since the former is
+stable but the latter could change if the list is moved.
- >>> service.find_members('anne@example.com', 'test@example.com')
+ >>> service.find_members('anne@example.com', 'test.example.com')
[<Member: anne <anne@example.com> on test@example.com
as MemberRole.member>,
<Member: anne <anne@example.com> on test@example.com
@@ -203,7 +205,7 @@ You can find all the memberships for an address with a specific role.
You can also find a specific membership by all three criteria.
- >>> service.find_members('anne@example.com', 'test@example.com',
+ >>> service.find_members('anne@example.com', 'test.example.com',
... MemberRole.owner)
[<Member: anne <anne@example.com> on test@example.com
as MemberRole.owner>]
diff --git a/src/mailman/app/events.py b/src/mailman/app/events.py
index a4f385239..28d78e001 100644
--- a/src/mailman/app/events.py
+++ b/src/mailman/app/events.py
@@ -41,7 +41,7 @@ def initialize():
domain.handle_DomainDeletingEvent,
moderator.handle_ListDeletingEvent,
passwords.handle_ConfigurationUpdatedEvent,
- subscriptions.handle_ListDeletedEvent,
+ subscriptions.handle_ListDeletingEvent,
switchboard.handle_ConfigurationUpdatedEvent,
i18n.handle_ConfigurationUpdatedEvent,
style_manager.handle_ConfigurationUpdatedEvent,
diff --git a/src/mailman/app/lifecycle.py b/src/mailman/app/lifecycle.py
index 5082034bc..326498478 100644
--- a/src/mailman/app/lifecycle.py
+++ b/src/mailman/app/lifecycle.py
@@ -89,23 +89,19 @@ def create_list(fqdn_listname, owners=None):
-def remove_list(fqdn_listname, mailing_list=None):
+def remove_list(mlist):
"""Remove the list and all associated artifacts and subscriptions."""
+ fqdn_listname = mlist.fqdn_listname
removeables = []
- # mailing_list will be None when only residual archives are being removed.
- if mailing_list is not None:
- # Remove all subscriptions, regardless of role.
- for member in mailing_list.subscribers.members:
- member.unsubscribe()
- # Delete the mailing list from the database.
- getUtility(IListManager).delete(mailing_list)
- # Do the MTA-specific list deletion tasks
- call_name(config.mta.incoming).create(mailing_list)
- # Remove the list directory.
- removeables.append(os.path.join(config.LIST_DATA_DIR, fqdn_listname))
+ # Delete the mailing list from the database.
+ getUtility(IListManager).delete(mlist)
+ # Do the MTA-specific list deletion tasks
+ call_name(config.mta.incoming).delete(mlist)
+ # Remove the list directory.
+ removeables.append(os.path.join(config.LIST_DATA_DIR, fqdn_listname))
# Remove any stale locks associated with the list.
for filename in os.listdir(config.LOCK_DIR):
- fn_listname = filename.split('.')[0]
+ fn_listname, dot, rest = filename.partition('.')
if fn_listname == fqdn_listname:
removeables.append(os.path.join(config.LOCK_DIR, filename))
# Now that we know what files and directories to delete, delete them.
diff --git a/src/mailman/app/subscriptions.py b/src/mailman/app/subscriptions.py
index 7949f83ee..3937d5b5c 100644
--- a/src/mailman/app/subscriptions.py
+++ b/src/mailman/app/subscriptions.py
@@ -22,7 +22,7 @@ from __future__ import absolute_import, print_function, unicode_literals
__metaclass__ = type
__all__ = [
'SubscriptionService',
- 'handle_ListDeletedEvent',
+ 'handle_ListDeletingEvent',
]
@@ -39,7 +39,7 @@ from mailman.core.constants import system_preferences
from mailman.database.transaction import dbconnection
from mailman.interfaces.address import IEmailValidator
from mailman.interfaces.listmanager import (
- IListManager, ListDeletedEvent, NoSuchListError)
+ IListManager, ListDeletingEvent, NoSuchListError)
from mailman.interfaces.member import DeliveryMode, MemberRole
from mailman.interfaces.subscriptions import (
ISubscriptionService, MissingUserError)
@@ -49,16 +49,12 @@ from mailman.model.member import Member
def _membership_sort_key(member):
- """Sort function for get_members().
+ """Sort function for find_members().
- The members are sorted first by fully-qualified mailing list name,
- then by subscribed email address, then by role.
+ The members are sorted first by unique list id, then by subscribed email
+ address, then by role.
"""
- # member.mailing_list is already the fqdn_listname, not the IMailingList
- # object.
- return (member.mailing_list,
- member.address.email,
- int(member.role))
+ return (member.list_id, member.address.email, int(member.role))
@@ -70,18 +66,18 @@ class SubscriptionService:
def get_members(self):
"""See `ISubscriptionService`."""
- # {fqdn_listname -> {role -> [members]}}
+ # {list_id -> {role -> [members]}}
by_list = {}
user_manager = getUtility(IUserManager)
for member in user_manager.members:
- by_role = by_list.setdefault(member.mailing_list, {})
+ by_role = by_list.setdefault(member.list_id, {})
members = by_role.setdefault(member.role.name, [])
members.append(member)
# Flatten into single list sorted as per the interface.
all_members = []
address_of_member = attrgetter('address.email')
- for fqdn_listname in sorted(by_list):
- by_role = by_list[fqdn_listname]
+ for list_id in sorted(by_list):
+ by_role = by_list[list_id]
all_members.extend(
sorted(by_role.get('owner', []), key=address_of_member))
all_members.extend(
@@ -103,14 +99,13 @@ class SubscriptionService:
return members[0]
@dbconnection
- def find_members(self, store,
- subscriber=None, fqdn_listname=None, role=None):
+ def find_members(self, store, subscriber=None, list_id=None, role=None):
"""See `ISubscriptionService`."""
# If `subscriber` is a user id, then we'll search for all addresses
# which are controlled by the user, otherwise we'll just search for
# the given address.
user_manager = getUtility(IUserManager)
- if subscriber is None and fqdn_listname is None and role is None:
+ if subscriber is None and list_id is None and role is None:
return []
# Querying for the subscriber is the most complicated part, because
# the parameter can either be an email address or a user id.
@@ -136,8 +131,8 @@ class SubscriptionService:
Member.address_id.is_in(address_ids)))
# Calculate the rest of the query expression, which will get And'd
# with the Or clause above (if there is one).
- if fqdn_listname is not None:
- query.append(Member.mailing_list == fqdn_listname)
+ if list_id is not None:
+ query.append(Member.list_id == list_id)
if role is not None:
query.append(Member.role == role)
results = store.find(Member, And(*query))
@@ -147,14 +142,14 @@ class SubscriptionService:
for member in self.get_members():
yield member
- def join(self, fqdn_listname, subscriber,
+ def join(self, list_id, subscriber,
display_name=None,
delivery_mode=DeliveryMode.regular,
role=MemberRole.member):
"""See `ISubscriptionService`."""
- mlist = getUtility(IListManager).get(fqdn_listname)
+ mlist = getUtility(IListManager).get_by_list_id(list_id)
if mlist is None:
- raise NoSuchListError(fqdn_listname)
+ raise NoSuchListError(list_id)
# Is the subscriber an email address or user id?
if isinstance(subscriber, basestring):
# It's an email address, so we'll want a real name. Make sure
@@ -181,23 +176,23 @@ class SubscriptionService:
raise MissingUserError(subscriber)
return mlist.subscribe(user, role)
- def leave(self, fqdn_listname, email):
+ def leave(self, list_id, email):
"""See `ISubscriptionService`."""
- mlist = getUtility(IListManager).get(fqdn_listname)
+ mlist = getUtility(IListManager).get_by_list_id(list_id)
if mlist is None:
- raise NoSuchListError(fqdn_listname)
+ raise NoSuchListError(list_id)
# XXX for now, no notification or user acknowledgment.
delete_member(mlist, email, False, False)
-def handle_ListDeletedEvent(event):
- """Delete a mailing list's members when the list is deleted."""
+def handle_ListDeletingEvent(event):
+ """Delete a mailing list's members when the list is being deleted."""
- if not isinstance(event, ListDeletedEvent):
+ if not isinstance(event, ListDeletingEvent):
return
# Find all the members still associated with the mailing list.
members = getUtility(ISubscriptionService).find_members(
- fqdn_listname=event.fqdn_listname)
+ list_id=event.mailing_list.list_id)
for member in members:
member.unsubscribe()
diff --git a/src/mailman/app/tests/test_membership.py b/src/mailman/app/tests/test_membership.py
index 626be8b08..00c279910 100644
--- a/src/mailman/app/tests/test_membership.py
+++ b/src/mailman/app/tests/test_membership.py
@@ -52,7 +52,7 @@ class AddMemberTest(unittest.TestCase):
'Anne Person', '123', DeliveryMode.regular,
system_preferences.preferred_language)
self.assertEqual(member.address.email, 'aperson@example.com')
- self.assertEqual(member.mailing_list, 'test@example.com')
+ self.assertEqual(member.list_id, 'test.example.com')
self.assertEqual(member.role, MemberRole.member)
def test_add_member_existing_user(self):
@@ -64,7 +64,7 @@ class AddMemberTest(unittest.TestCase):
'Anne Person', '123', DeliveryMode.regular,
system_preferences.preferred_language)
self.assertEqual(member.address.email, 'aperson@example.com')
- self.assertEqual(member.mailing_list, 'test@example.com')
+ self.assertEqual(member.list_id, 'test.example.com')
def test_add_member_banned(self):
# Test that members who are banned by specific address cannot
@@ -127,7 +127,7 @@ class AddMemberTest(unittest.TestCase):
system_preferences.preferred_language,
MemberRole.moderator)
self.assertEqual(member.address.email, 'aperson@example.com')
- self.assertEqual(member.mailing_list, 'test@example.com')
+ self.assertEqual(member.list_id, 'test.example.com')
self.assertEqual(member.role, MemberRole.moderator)
def test_add_member_twice(self):
@@ -159,7 +159,7 @@ class AddMemberTest(unittest.TestCase):
'Anne Person', '123', DeliveryMode.regular,
system_preferences.preferred_language,
MemberRole.owner)
- self.assertEqual(member_1.mailing_list, member_2.mailing_list)
+ self.assertEqual(member_1.list_id, member_2.list_id)
self.assertEqual(member_1.address, member_2.address)
self.assertEqual(member_1.user, member_2.user)
self.assertNotEqual(member_1.member_id, member_2.member_id)
diff --git a/src/mailman/app/tests/test_subscriptions.py b/src/mailman/app/tests/test_subscriptions.py
index a63c9ac04..1c37d4cb9 100644
--- a/src/mailman/app/tests/test_subscriptions.py
+++ b/src/mailman/app/tests/test_subscriptions.py
@@ -52,7 +52,7 @@ class TestJoin(unittest.TestCase):
def test_join_user_with_bogus_id(self):
# When `subscriber` is a missing user id, an exception is raised.
try:
- self._service.join('test@example.com', uuid.UUID(int=99))
+ self._service.join('test.example.com', uuid.UUID(int=99))
except MissingUserError as exc:
self.assertEqual(exc.user_id, uuid.UUID(int=99))
else:
@@ -62,7 +62,7 @@ class TestJoin(unittest.TestCase):
# When `subscriber` is a string that is not an email address, an
# exception is raised.
try:
- self._service.join('test@example.com', 'bogus')
+ self._service.join('test.example.com', 'bogus')
except InvalidEmailAddressError as exc:
self.assertEqual(exc.email, 'bogus')
else: