summaryrefslogtreecommitdiff
path: root/src/mailman/model/subscriptions.py
diff options
context:
space:
mode:
authorBarry Warsaw2016-01-06 22:57:06 -0500
committerBarry Warsaw2016-01-06 22:57:06 -0500
commit4c9487d371a3b61de4533a267ee934c86a1b3237 (patch)
tree74e2e03cdc2d74eb309cb66876992b7ab393799c /src/mailman/model/subscriptions.py
parenta4bbc7c4fcea5596ab9f5a3d82983ddcf6d25909 (diff)
downloadmailman-4c9487d371a3b61de4533a267ee934c86a1b3237.tar.gz
mailman-4c9487d371a3b61de4533a267ee934c86a1b3237.tar.zst
mailman-4c9487d371a3b61de4533a267ee934c86a1b3237.zip
Diffstat (limited to 'src/mailman/model/subscriptions.py')
-rw-r--r--src/mailman/model/subscriptions.py131
1 files changed, 131 insertions, 0 deletions
diff --git a/src/mailman/model/subscriptions.py b/src/mailman/model/subscriptions.py
new file mode 100644
index 000000000..d1b805ef1
--- /dev/null
+++ b/src/mailman/model/subscriptions.py
@@ -0,0 +1,131 @@
+# Copyright (C) 2016 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 <http://www.gnu.org/licenses/>.
+
+"""Subscription services."""
+
+__all__ = [
+ 'SubscriptionService',
+ ]
+
+
+from mailman.app.membership import delete_member
+from mailman.database.transaction import dbconnection
+from mailman.interfaces.listmanager import IListManager, NoSuchListError
+from mailman.interfaces.subscriptions import ISubscriptionService
+from mailman.interfaces.usermanager import IUserManager
+from mailman.model.address import Address
+from mailman.model.member import Member
+from mailman.model.user import User
+from mailman.utilities.queries import QuerySequence
+from operator import attrgetter
+from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound
+from zope.component import getUtility
+from zope.interface import implementer
+
+
+@implementer(ISubscriptionService)
+class SubscriptionService:
+ """Subscription services for the REST API."""
+
+ __name__ = 'members'
+
+ def get_members(self):
+ """See `ISubscriptionService`."""
+ # {list_id -> {role -> [members]}}
+ by_list = {}
+ user_manager = getUtility(IUserManager)
+ for member in user_manager.members:
+ 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 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(
+ sorted(by_role.get('moderator', []), key=address_of_member))
+ all_members.extend(
+ sorted(by_role.get('member', []), key=address_of_member))
+ return all_members
+
+ @dbconnection
+ def get_member(self, store, member_id):
+ """See `ISubscriptionService`."""
+ members = store.query(Member).filter(Member._member_id == member_id)
+ if members.count() == 0:
+ return None
+ else:
+ assert members.count() == 1, 'Too many matching members'
+ return members[0]
+
+ @dbconnection
+ 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.
+ if subscriber is None and list_id is None and role is None:
+ return []
+ order = (Member.list_id, Address.email, Member.role)
+ # Querying for the subscriber is the most complicated part, because
+ # the parameter can either be an email address or a user id. Start by
+ # building two queries, one joined on the member's address, and one
+ # joined on the member's user. Add the resulting email address to the
+ # selected values to be able to sort on it later on.
+ q_address = store.query(Member, Address.email).join(Member._address)
+ q_user = store.query(Member, Address.email).join(Member._user)
+ if subscriber is not None:
+ if isinstance(subscriber, str):
+ # subscriber is an email address.
+ q_address = q_address.filter(
+ Address.email == subscriber.lower())
+ q_user = q_user.join(User.addresses).filter(
+ Address.email == subscriber.lower())
+ else:
+ # subscriber is a user id.
+ q_address = q_address.join(Address.user).filter(
+ User._user_id == subscriber)
+ q_user = q_user.join(User._preferred_address).filter(
+ User._user_id == subscriber)
+ # Add additional filters to both queries.
+ if list_id is not None:
+ q_address = q_address.filter(Member.list_id == list_id)
+ q_user = q_user.filter(Member.list_id == list_id)
+ if role is not None:
+ q_address = q_address.filter(Member.role == role)
+ q_user = q_user.filter(Member.role == role)
+ # Do a UNION of the two queries, sort the result and generate Members.
+ try:
+ query = q_address.union(q_user).order_by(*order).from_self(Member)
+ except NoResultFound:
+ query = None
+ return QuerySequence(query)
+
+ def __iter__(self):
+ for member in self.get_members():
+ yield member
+
+ def leave(self, list_id, email):
+ """See `ISubscriptionService`."""
+ mlist = getUtility(IListManager).get_by_list_id(list_id)
+ if mlist is None:
+ raise NoSuchListError(list_id)
+ # XXX for now, no notification or user acknowledgment.
+ delete_member(mlist, email, False, False)