diff options
| author | Barry Warsaw | 2012-09-04 21:31:50 -0400 |
|---|---|---|
| committer | Barry Warsaw | 2012-09-04 21:31:50 -0400 |
| commit | 3c77c83ec4070e7183482445ff0b9a30398b72f4 (patch) | |
| tree | 7d992c11114cc2e8cb1c22d65f517c1d1f5f201f /src/mailman/app | |
| parent | 56c3bf127b5fb912461e638b6936c627d30be744 (diff) | |
| download | mailman-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.py | 3 | ||||
| -rw-r--r-- | src/mailman/app/docs/lifecycle.rst | 2 | ||||
| -rw-r--r-- | src/mailman/app/docs/subscriptions.rst | 24 | ||||
| -rw-r--r-- | src/mailman/app/events.py | 2 | ||||
| -rw-r--r-- | src/mailman/app/lifecycle.py | 22 | ||||
| -rw-r--r-- | src/mailman/app/subscriptions.py | 53 | ||||
| -rw-r--r-- | src/mailman/app/tests/test_membership.py | 8 | ||||
| -rw-r--r-- | src/mailman/app/tests/test_subscriptions.py | 4 |
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: |
