1 # Copyright (C) 2016-2019 by the Free Software Foundation, Inc.
3 # This file is part of GNU Mailman.
5 # GNU Mailman is free software: you can redistribute it and/or modify it under
6 # the terms of the GNU General Public License as published by the Free
7 # Software Foundation, either version 3 of the License, or (at your option)
10 # GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 # You should have received a copy of the GNU General Public License along with
16 # GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
18 """Subscription services."""
20 from mailman
.app
.membership
import delete_member
21 from mailman
.database
.transaction
import dbconnection
22 from mailman
.interfaces
.listmanager
import IListManager
, NoSuchListError
23 from mailman
.interfaces
.member
import MemberRole
24 from mailman
.interfaces
.subscriptions
import (
25 ISubscriptionService
, TooManyMembersError
)
26 from mailman
.interfaces
.usermanager
import IUserManager
27 from mailman
.model
.address
import Address
28 from mailman
.model
.member
import Member
29 from mailman
.model
.user
import User
30 from mailman
.utilities
.queries
import QuerySequence
31 from operator
import attrgetter
32 from public
import public
33 from sqlalchemy
.orm
.exc
import MultipleResultsFound
, NoResultFound
34 from zope
.component
import getUtility
35 from zope
.interface
import implementer
39 @implementer(ISubscriptionService
)
40 class SubscriptionService
:
41 """Subscription services for the REST API."""
45 def get_members(self
):
46 """See `ISubscriptionService`."""
47 # {list_id -> {role -> [members]}}
49 user_manager
= getUtility(IUserManager
)
50 for member
in user_manager
.members
:
51 by_role
= by_list
.setdefault(member
.list_id
, {})
52 members
= by_role
.setdefault(member
.role
.name
, [])
53 members
.append(member
)
54 # Flatten into single list sorted as per the interface.
56 address_of_member
= attrgetter('address.email')
57 for list_id
in sorted(by_list
):
58 by_role
= by_list
[list_id
]
60 sorted(by_role
.get('owner', []), key
=address_of_member
))
62 sorted(by_role
.get('moderator', []), key
=address_of_member
))
64 sorted(by_role
.get('member', []), key
=address_of_member
))
68 def get_member(self
, store
, member_id
):
69 """See `ISubscriptionService`."""
70 members
= store
.query(Member
).filter(Member
._member
_id
== member_id
)
71 if members
.count() == 0:
74 assert members
.count() == 1, 'Too many matching members'
78 def _find_members(self
, store
, subscriber
, list_id
, role
):
79 # If `subscriber` is a user id, then we'll search for all addresses
80 # which are controlled by the user, otherwise we'll just search for
82 if subscriber
is None and list_id
is None and role
is None:
84 order
= (Member
.list_id
, Address
.email
, Member
.role
)
85 # Querying for the subscriber is the most complicated part, because
86 # the parameter can either be an email address or a user id. Start by
87 # building two queries, one joined on the member's address, and one
88 # joined on the member's user. Add the resulting email address to the
89 # selected values to be able to sort on it later on.
90 q_address
= store
.query(Member
, Address
.email
).join(Member
._address
)
91 q_user
= store
.query(Member
, Address
.email
).join(
92 User
, User
.id == Member
.user_id
).join(User
._preferred
_address
)
93 if subscriber
is not None:
94 if isinstance(subscriber
, str):
95 # subscriber is an email address.
96 subscriber
= subscriber
.lower()
98 subscriber
= subscriber
.replace('*', '%')
99 q_address
= q_address
.filter(
100 Address
.email
.like(subscriber
))
101 q_user
= q_user
.filter(Address
.email
.like(subscriber
))
103 q_address
= q_address
.filter(Address
.email
== subscriber
)
104 q_user
= q_user
.filter(Address
.email
== subscriber
)
106 # subscriber is a user id.
107 q_address
= q_address
.join(Address
.user
).filter(
108 User
._user
_id
== subscriber
)
109 q_user
= q_user
.filter(User
._user
_id
== subscriber
)
111 # We're not searching for a subscriber so only select preferred
112 # addresses (see GL issue 227).
113 q_user
= q_user
.filter(Address
.id == User
._preferred
_address
_id
)
114 # Add additional filters to both queries.
115 if list_id
is not None:
116 q_address
= q_address
.filter(Member
.list_id
== list_id
)
117 q_user
= q_user
.filter(Member
.list_id
== list_id
)
119 q_address
= q_address
.filter(Member
.role
== role
)
120 q_user
= q_user
.filter(Member
.role
== role
)
121 # Do a UNION of the two queries, sort the result and generate Members.
122 return q_address
.union(q_user
).order_by(*order
).from_self(Member
)
124 def find_members(self
, subscriber
=None, list_id
=None, role
=None):
125 """See `ISubscriptionService`."""
126 return QuerySequence(self
._find
_members
(subscriber
, list_id
, role
))
128 def find_member(self
, subscriber
=None, list_id
=None, role
=None):
129 """See `ISubscriptionService`."""
131 result
= self
._find
_members
(subscriber
, list_id
, role
)
132 return (result
if result
is None else result
.one())
133 except NoResultFound
:
135 except MultipleResultsFound
:
136 # Coerce the exception into a Mailman-layer exception so call
137 # sites don't have to import from SQLAlchemy, resulting in a layer
139 raise TooManyMembersError(subscriber
, list_id
, role
)
142 yield from self
.get_members()
144 def leave(self
, list_id
, email
):
145 """See `ISubscriptionService`."""
146 mlist
= getUtility(IListManager
).get_by_list_id(list_id
)
148 raise NoSuchListError(list_id
)
149 # XXX for now, no notification or user acknowledgment.
150 delete_member(mlist
, email
, False, False)
153 def unsubscribe_members(self
, store
, list_id
, emails
):
154 """See 'ISubscriptionService'."""
157 mlist
= getUtility(IListManager
).get_by_list_id(list_id
)
159 raise NoSuchListError(list_id
)
160 # Start with a query on the matching list-id and role.
161 q_member
= store
.query(Member
).filter(
162 Member
.list_id
== list_id
,
163 Member
.role
== MemberRole
.member
)
165 for email
in set(emails
):
167 # Join with a queries matching the email address and preferred
168 # address of any subscribed user.
169 q_address
= q_member
.join(Member
._address
).filter(
170 Address
.email
== email
)
171 q_user
= q_member
.join(Member
._user
).join(
172 User
._preferred
_address
).filter(Address
.email
== email
)
173 members
= q_address
.union(q_user
).all()
174 for member
in members
:
177 (success
if unsubscribed
else fail
).add(email
)