1 # Copyright (C) 2007-2023 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 <https://www.gnu.org/licenses/>.
18 """Interface describing the basics of a member."""
21 from mailman
.core
.i18n
import _
22 from mailman
.interfaces
.errors
import MailmanError
23 from public
import public
24 from zope
.interface
import Attribute
, Interface
28 class DeliveryMode(Enum
):
29 # Regular (i.e. non-digest) delivery
31 # Plain text digest delivery
33 # MIME digest delivery
40 class DeliveryStatus(Enum
):
43 # Delivery was disabled by the user
45 # Delivery was disabled due to bouncing addresses
47 # Delivery was disabled by an administrator or moderator
49 # Disabled for unknown reasons.
54 class SubscriptionMode(Enum
):
55 """The Membership is via an Address or a User (primary address)"""
56 # Subscribed as user via primary address.
58 # Subscribed via specific address.
63 class MemberRole(Enum
):
71 class MembershipChangeEvent
:
72 """Base class for subscription/unsubscription events."""
74 def __init__(self
, mlist
, member
):
80 class SubscriptionEvent(MembershipChangeEvent
):
81 """Event which gets triggered when a user joins a mailing list."""
83 def __init__(self
, *args
, **kw
):
84 send_welcome_message
= kw
.pop('send_welcome_message', None)
85 super().__init
__(*args
, **kw
)
86 self
.send_welcome_message
= send_welcome_message
89 return '{0} joined {1}'.format(self
.member
.address
, self
.mlist
.list_id
)
93 class UnsubscriptionEvent(MembershipChangeEvent
):
94 """Event which gets triggered when a user leaves a mailing list.
96 One thing to keep in mind: because the IMember is deleted when the
97 unsubscription happens, this event actually gets triggered just before the
98 member is unsubscribed.
102 return '{0} left {1}'.format(self
.member
.address
, self
.mlist
.list_id
)
106 class MembershipError(MailmanError
):
107 """Base exception for all membership errors."""
111 class AlreadySubscribedError(MembershipError
):
112 """The member is already subscribed to the mailing list with this role."""
114 def __init__(self
, fqdn_listname
, email
, role
):
116 self
.fqdn_listname
= fqdn_listname
121 if self
.role
== MemberRole
.member
:
122 return _('${self.email} is already a member of mailing list '
123 '${self.fqdn_listname}')
124 if self
.role
== MemberRole
.owner
:
125 return _('${self.email} is already an owner of mailing list '
126 '${self.fqdn_listname}')
127 if self
.role
== MemberRole
.moderator
:
128 return _('${self.email} is already a moderator of mailing list '
129 '${self.fqdn_listname}')
130 if self
.role
== MemberRole
.nonmember
:
131 return _('${self.email} is already a non-member of mailing list '
132 '${self.fqdn_listname}')
136 class MembershipIsBannedError(MembershipError
):
137 """The address is not allowed to subscribe to the mailing list."""
139 def __init__(self
, mlist
, address
):
142 self
._address
= address
145 return '{0} is not allowed to subscribe to {1.fqdn_listname}'.format(
146 self
._address
, self
._mlist
)
150 class MissingPreferredAddressError(MembershipError
):
151 """A user without a preferred address attempted to subscribe."""
153 def __init__(self
, user
):
158 return 'User must have a preferred address: {0}'.format(self
._user
)
162 class NotAMemberError(MembershipError
):
163 """The address is not a member of the mailing list."""
165 def __init__(self
, mlist
, address
):
168 self
._address
= address
171 return '{0} is not a member of {1.fqdn_listname}'.format(
172 self
._address
, self
._mlist
)
176 class IMember(Interface
):
177 """A member of a mailing list."""
179 member_id
= Attribute(
180 """The member's unique, random identifier as a UUID.""")
183 """The list id of the mailing list the member is subscribed to.""")
185 mailing_list
= Attribute(
186 """The `IMailingList` that the member is subscribed to.""")
189 """The email address that's subscribed to the list.""")
192 """The user associated with this member.""")
194 subscriber
= Attribute(
195 """The object representing how this member is subscribed.
197 This will be an ``IAddress`` if the user is subscribed via an explicit
198 address, otherwise if the user is subscribed via their preferred
199 address, it will be an ``IUser``.
202 display_name
= Attribute(
203 """The best match of the member's display name.
205 This will be `subscriber.display_name` if available, which means it
206 will either be the display name of the address or user that's
207 subscribed. If unavailable, and the address is the subscriber, then
208 the linked user's display name is given, if available. When all else
209 fails, the empty string is returned.
212 preferences
= Attribute(
213 """This member's preferences.""")
216 """The role of this membership.""")
218 moderation_action
= Attribute(
219 """The moderation action for this member as an `Action`.""")
222 """Unsubscribe (and delete) this member from the mailing list."""
224 acknowledge_posts
= Attribute(
225 """Send an acknowledgment for every posting?
227 Unlike going through the preferences, this attribute return the
228 preference value based on the following lookup order:
236 preferred_language
= Attribute(
237 """The preferred language for interacting with a mailing list.
239 Unlike going through the preferences, this attribute return the
240 preference value based on the following lookup order:
248 receive_list_copy
= Attribute(
249 """Should an explicit recipient receive a list copy?
251 Unlike going through `preferences`, this attribute returns the
252 preference value based on the following lookup order:
260 receive_own_postings
= Attribute(
261 """Should the poster get a list copy of their own messages?
263 Unlike going through `preferences`, this attribute returns the
264 preference value based on the following lookup order:
272 delivery_mode
= Attribute(
273 """The preferred delivery mode.
275 Unlike going through `preferences`, this attribute returns the
276 preference value based on the following lookup order:
284 delivery_status
= Attribute(
285 """The delivery status.
287 Unlike going through `preferences`, this attribute returns the
288 preference value based on the following lookup order:
295 XXX I'm not sure this is the right place to put this.""")
297 bounce_score
= Attribute(
298 """The bounce score of this address for the list_id.
300 This is a metric of how much the emails sent to this address bounces.
301 This value is incremented first time a bounce is received for the
302 address and mailing list pair on that day.
304 The bounce_score of an address on one mailing list does not affect
305 their bounce_score on other mailing lists.
308 last_bounce_received
= Attribute(
309 """The last time when a bounce was received for this mailing list
313 last_warnings_sent
= Attribute(
314 """The last time when a warning email was sent to the address""")
316 total_warnings_sent
= Attribute(
317 """Total number of warnings sent to the address. """)
319 disabled
= Attribute(
320 """Has the email delivery been disabled. """)
322 subscription_mode
= Attribute(
323 """Is the user subscribed via their email address or primary address.
326 def reset_bounce_info():
327 """Reset the bounce related information. """
331 class IMembershipManager(Interface
):
332 """Member object manager."""
334 def memberships_pending_warning():
335 """Memberships that have been disabled due to excessive bounces and
336 require a warning to be sent.
338 The total number of warnings sent to these are less than the
339 MailingList's configured number of warnings to be sent before the
340 membership is removed.
343 def memberships_pending_removal():
344 """Memberships pending removal.
346 These memberships have maximum number of warnings already sent out and