From 6e7bfd50436c702aea5d392adcf2d63340ed3f69 Mon Sep 17 00:00:00 2001 From: Barry Warsaw Date: Wed, 17 Aug 2011 19:10:39 -0400 Subject: Basic infrastructure for fixing bug 827036. * Use zope.events to signal when a mailing list has been created or deleted. * Register a handler for the ListDeletedEvent which cleans up member subscriptions. * Relax the criteria for find_members(), both internally and in the REST API, so that the subscriber is not required. E.g. you can now find all members of a mailing list. --- src/mailman/app/docs/subscriptions.rst | 13 +++++- src/mailman/app/events.py | 38 ++++++++++++++++ src/mailman/app/subscriptions.py | 81 ++++++++++++++++++++-------------- 3 files changed, 98 insertions(+), 34 deletions(-) create mode 100644 src/mailman/app/events.py (limited to 'src/mailman/app') diff --git a/src/mailman/app/docs/subscriptions.rst b/src/mailman/app/docs/subscriptions.rst index 378dd0a40..8291132ce 100644 --- a/src/mailman/app/docs/subscriptions.rst +++ b/src/mailman/app/docs/subscriptions.rst @@ -79,7 +79,7 @@ If you know the member id for a specific member, you can get that member. on test@example.com as MemberRole.owner> If you know the member's address, you can find all their memberships, based on -specific search criteria. At a minimum, you need the member's email address. +specific search criteria. :: >>> mlist2 = create_list('foo@example.com') @@ -121,6 +121,17 @@ Memberships can also be searched for by user id. on test@example.com as MemberRole.moderator>] +You can find all the memberships for a specific mailing list. + + >>> service.find_members(fqdn_listname='test@example.com') + [ on test@example.com + as MemberRole.member>, + on test@example.com as MemberRole.owner>, + on test@example.com + as MemberRole.moderator>, + on test@example.com + as MemberRole.member>] + You can find all the memberships for an address on a specific mailing list. >>> service.find_members('anne@example.com', 'test@example.com') diff --git a/src/mailman/app/events.py b/src/mailman/app/events.py new file mode 100644 index 000000000..4beac8212 --- /dev/null +++ b/src/mailman/app/events.py @@ -0,0 +1,38 @@ +# Copyright (C) 2011 by the Free Software Foundation, Inc. +# +# This file is part of GNU Mailman. +# +# GNU Mailman is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# GNU Mailman. If not, see . + +"""Global events.""" + +from __future__ import absolute_import, unicode_literals + +__metaclass__ = type +__all__ = [ + 'initialize', + ] + + +from zope import event + +from mailman.app.subscriptions import handle_ListDeleteEvent + + + +def initialize(): + """Initialize global event subscribers.""" + event.subscribers.extend([ + handle_ListDeleteEvent, + ]) diff --git a/src/mailman/app/subscriptions.py b/src/mailman/app/subscriptions.py index ab8c3d53c..57609a006 100644 --- a/src/mailman/app/subscriptions.py +++ b/src/mailman/app/subscriptions.py @@ -22,6 +22,7 @@ from __future__ import absolute_import, unicode_literals __metaclass__ = type __all__ = [ 'SubscriptionService', + 'handle_ListDeleteEvent', ] @@ -34,7 +35,8 @@ from mailman.app.membership import add_member, delete_member from mailman.config import config from mailman.core.constants import system_preferences from mailman.interfaces.address import InvalidEmailAddressError -from mailman.interfaces.listmanager import IListManager, NoSuchListError +from mailman.interfaces.listmanager import ( + IListManager, ListDeletedEvent, NoSuchListError) from mailman.interfaces.member import DeliveryMode from mailman.interfaces.subscriptions import ( ISubscriptionService, MissingUserError) @@ -93,43 +95,43 @@ class SubscriptionService: assert members.count() == 1, 'Too many matching members' return members[0] - def find_members(self, subscriber, fqdn_listname=None, role=None): + def find_members(self, subscriber=None, fqdn_listname=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) - query = None - if isinstance(subscriber, basestring): - # subscriber is an email address. - address = user_manager.get_address(subscriber) - user = user_manager.get_user(subscriber) - # This probably could be made more efficient. - if address is None or user is None: - return [] - or_clause = Or(Member.address_id == address.id, - Member.user_id == user.id) - else: - # subscriber is a user id. - user = user_manager.get_user_by_id(unicode(subscriber)) - address_ids = list(address.id for address in user.addresses - if address.id is not None) - if len(address_ids) == 0 or user is None: - return [] - or_clause = Or(Member.user_id == user.id, - Member.address_id.is_in(address_ids)) - # The rest is the same, based on the given criteria. - if fqdn_listname is None and role is None: - query = or_clause - elif fqdn_listname is None: - query = And(Member.role == role, or_clause) - elif role is None: - query = And(Member.mailing_list == fqdn_listname, or_clause) - else: - query = And(Member.mailing_list == fqdn_listname, - Member.role == role, - or_clause) - results = config.db.store.find(Member, query) + if subscriber is None and fqdn_listname 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. + query = [] + if subscriber is not None: + if isinstance(subscriber, basestring): + # subscriber is an email address. + address = user_manager.get_address(subscriber) + user = user_manager.get_user(subscriber) + # This probably could be made more efficient. + if address is None or user is None: + return [] + query.append(Or(Member.address_id == address.id, + Member.user_id == user.id)) + else: + # subscriber is a user id. + user = user_manager.get_user_by_id(unicode(subscriber)) + address_ids = list(address.id for address in user.addresses + if address.id is not None) + if len(address_ids) == 0 or user is None: + return [] + query.append(Or(Member.user_id == user.id, + 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 role is not None: + query.append(Member.role == role) + results = config.db.store.find(Member, And(*query)) return sorted(results, key=_membership_sort_key) def __iter__(self): @@ -177,3 +179,16 @@ class SubscriptionService: raise NoSuchListError(fqdn_listname) # XXX for now, no notification or user acknowledgement. delete_member(mlist, address, False, False) + + + +def handle_ListDeleteEvent(event): + """Delete a mailing list's members when the list is deleted.""" + + if not isinstance(event, ListDeletedEvent): + return + # Find all the members still associated with the mailing list. + members = getUtility(ISubscriptionService).find_members( + fqdn_listname=event.fqdn_listname) + for member in members: + member.unsubscribe() -- cgit v1.2.3-70-g09d2