Make the text of AlreadySubscribedError translatable
[mailman.git] / src / mailman / interfaces / member.py
blobf82127edd15846667408e42a59051385f96fc78c
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)
8 # any later version.
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
13 # more details.
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."""
20 from enum import Enum
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
27 @public
28 class DeliveryMode(Enum):
29 # Regular (i.e. non-digest) delivery
30 regular = 1
31 # Plain text digest delivery
32 plaintext_digests = 2
33 # MIME digest delivery
34 mime_digests = 3
35 # Summary digests
36 summary_digests = 4
39 @public
40 class DeliveryStatus(Enum):
41 # Delivery is enabled
42 enabled = 1
43 # Delivery was disabled by the user
44 by_user = 2
45 # Delivery was disabled due to bouncing addresses
46 by_bounces = 3
47 # Delivery was disabled by an administrator or moderator
48 by_moderator = 4
49 # Disabled for unknown reasons.
50 unknown = 5
53 @public
54 class SubscriptionMode(Enum):
55 """The Membership is via an Address or a User (primary address)"""
56 # Subscribed as user via primary address.
57 as_user = 1
58 # Subscribed via specific address.
59 as_address = 2
62 @public
63 class MemberRole(Enum):
64 member = 1
65 owner = 2
66 moderator = 3
67 nonmember = 4
70 @public
71 class MembershipChangeEvent:
72 """Base class for subscription/unsubscription events."""
74 def __init__(self, mlist, member):
75 self.mlist = mlist
76 self.member = member
79 @public
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
88 def __str__(self):
89 return '{0} joined {1}'.format(self.member.address, self.mlist.list_id)
92 @public
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.
99 """
101 def __str__(self):
102 return '{0} left {1}'.format(self.member.address, self.mlist.list_id)
105 @public
106 class MembershipError(MailmanError):
107 """Base exception for all membership errors."""
110 @public
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):
115 super().__init__()
116 self.fqdn_listname = fqdn_listname
117 self.email = email
118 self.role = role
120 def __str__(self):
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}')
135 @public
136 class MembershipIsBannedError(MembershipError):
137 """The address is not allowed to subscribe to the mailing list."""
139 def __init__(self, mlist, address):
140 super().__init__()
141 self._mlist = mlist
142 self._address = address
144 def __str__(self):
145 return '{0} is not allowed to subscribe to {1.fqdn_listname}'.format(
146 self._address, self._mlist)
149 @public
150 class MissingPreferredAddressError(MembershipError):
151 """A user without a preferred address attempted to subscribe."""
153 def __init__(self, user):
154 super().__init__()
155 self._user = user
157 def __str__(self):
158 return 'User must have a preferred address: {0}'.format(self._user)
161 @public
162 class NotAMemberError(MembershipError):
163 """The address is not a member of the mailing list."""
165 def __init__(self, mlist, address):
166 super().__init__()
167 self._mlist = mlist
168 self._address = address
170 def __str__(self):
171 return '{0} is not a member of {1.fqdn_listname}'.format(
172 self._address, self._mlist)
175 @public
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.""")
182 list_id = Attribute(
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.""")
188 address = Attribute(
189 """The email address that's subscribed to the list.""")
191 user = Attribute(
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``.
200 """)
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.
210 """)
212 preferences = Attribute(
213 """This member's preferences.""")
215 role = Attribute(
216 """The role of this membership.""")
218 moderation_action = Attribute(
219 """The moderation action for this member as an `Action`.""")
221 def unsubscribe():
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:
230 1. The member
231 2. The address
232 3. The user
233 4. System default
234 """)
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:
242 1. The member
243 2. The address
244 3. The user
245 4. System default
246 """)
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:
254 1. The member
255 2. The address
256 3. The user
257 4. System default
258 """)
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:
266 1. The member
267 2. The address
268 3. The user
269 4. System default
270 """)
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:
278 1. The member
279 2. The address
280 3. The user
281 4. System default
282 """)
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:
290 1. The member
291 2. The address
292 3. The user
293 4. System default
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.
306 """)
308 last_bounce_received = Attribute(
309 """The last time when a bounce was received for this mailing list
310 address pair.
311 """)
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.
324 """)
326 def reset_bounce_info():
327 """Reset the bounce related information. """
330 @public
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
347 are pending removal.