~launchpad-pqm/launchpad/devel

9965.7.2 by Barry Warsaw
Get rid of the top-level +mailinglists url and fix the test failure fallout.
1
==================
2
Team Mailing Lists
3
==================
4419.6.1 by Barry Warsaw
Fleshed out the interface for IMailingList and IMailingListRegistry. Added
4
4543.2.12 by Barry Warsaw
A round of changes in response to BradC's review:
5
Teams may have at most one team mailing list.  Creating a team mailing list
4419.6.1 by Barry Warsaw
Fleshed out the interface for IMailingList and IMailingListRegistry. Added
6
requires several steps, starting with registration of the list by the owner of
4543.2.14 by Barry Warsaw
Last round of response to Kiko's review.
7
an existing team.  This is done through an IMailingListSet utility.
4419.6.1 by Barry Warsaw
Fleshed out the interface for IMailingList and IMailingListRegistry. Added
8
4682.1.6 by Christian Reis
Use webapp.testing's verifyObject in all doctests, which copes with security-proxied objects, and remove the crack I added to the bug and bugtask uses of it.
9
    >>> from canonical.launchpad.webapp.testing import verifyObject
11716.1.15 by Curtis Hovey
Fixed multiline import statements in doctests.
10
    >>> from lp.registry.interfaces.mailinglist import (
11716.1.12 by Curtis Hovey
Sorted imports in doctests.
11
    ...     IMailingList,
12
    ...     IMailingListSet,
13
    ...     )
4543.2.14 by Barry Warsaw
Last round of response to Kiko's review.
14
    >>> list_set = getUtility(IMailingListSet)
15
    >>> verifyObject(IMailingListSet, list_set)
4419.6.1 by Barry Warsaw
Fleshed out the interface for IMailingList and IMailingListRegistry. Added
16
    True
17
18
In the following description of how to use team mailing lists, we will need
9965.7.2 by Barry Warsaw
Get rid of the top-level +mailinglists url and fix the test failure fallout.
19
several some teams.
4419.6.1 by Barry Warsaw
Fleshed out the interface for IMailingList and IMailingListRegistry. Added
20
7675.110.3 by Curtis Hovey
Ran the migration script to move registry code to lp.registry.
21
    >>> from lp.registry.tests.mailinglists_helper import new_team
4543.2.14 by Barry Warsaw
Last round of response to Kiko's review.
22
    >>> team_one = new_team('team-one')
23
    >>> team_two = new_team('team-two')
4419.6.1 by Barry Warsaw
Fleshed out the interface for IMailingList and IMailingListRegistry. Added
24
4897.4.11 by Barry Warsaw
By generalizing the mailing lists test helper module away from just XMLRPC, we
25
    # Define a helper function that sorts mailing lists alphabetically based
9965.7.2 by Barry Warsaw
Get rid of the top-level +mailinglists url and fix the test failure fallout.
26
    # on their team's name.  We can't use operator.attrgetter() because until
27
    # Python 2.6, that traverses only one level of attribute.
4419.6.3 by Barry Warsaw
More fleshing out of the mailing list database classes and doctest.
28
    >>> def sorted_lists(lists):
29
    ...     return sorted(lists, key=lambda L: L.team.name)
30
4419.6.1 by Barry Warsaw
Fleshed out the interface for IMailingList and IMailingListRegistry. Added
31
None of these teams have mailing lists yet.
32
4543.2.14 by Barry Warsaw
Last round of response to Kiko's review.
33
    >>> sorted_lists(list_set.approved_lists)
34
    []
4897.2.1 by Barry Warsaw
Mailing list subscriptions.
35
    >>> sorted_lists(list_set.active_lists)
36
    []
4543.2.14 by Barry Warsaw
Last round of response to Kiko's review.
37
    >>> print list_set.get(team_one.name)
38
    None
39
    >>> print list_set.get(team_two.name)
40
    None
9965.7.2 by Barry Warsaw
Get rid of the top-level +mailinglists url and fix the test failure fallout.
41
42
43
Creating a team mailing list
44
============================
45
46
When a mailing list is created, it is automatically set to the APPROVED state,
47
meaning that list requests are automatically approved.  This doesn't actually
48
create the list until Mailman acts on the mailing list creation request.
49
4543.2.14 by Barry Warsaw
Last round of response to Kiko's review.
50
    >>> list_one = list_set.new(team_one)
4419.6.6 by Barry Warsaw
One typo fix and one additional small test.
51
    >>> verifyObject(IMailingList, list_one)
52
    True
4419.6.1 by Barry Warsaw
Fleshed out the interface for IMailingList and IMailingListRegistry. Added
53
    >>> list_one
12043.6.3 by Curtis Hovey
Add address to mailing list repr.
54
    <MailingList for team "team-one"; status=APPROVED;
55
     address=team-one@lists.launchpad.dev at 0x...>
4419.6.3 by Barry Warsaw
More fleshing out of the mailing list database classes and doctest.
56
5256.1.2 by Barry Warsaw
Changes in response to jml's review:
57
You can always access the mailing list through its team, if the team has a
58
mailing list.
59
60
    >>> team_one.mailing_list
12043.6.3 by Curtis Hovey
Add address to mailing list repr.
61
    <MailingList for team "team-one"; status=APPROVED; ...>
5256.1.2 by Barry Warsaw
Changes in response to jml's review:
62
    >>> print team_two.mailing_list
63
    None
64
4419.6.3 by Barry Warsaw
More fleshing out of the mailing list database classes and doctest.
65
You may not register a mailing list for a person.
66
6130.6.3 by Maris Fogels
Rewrote the team join pages and the mailing list doctests to take the new behaviour into account.
67
    >>> login('foo.bar@canonical.com')
7315.6.1 by Barry Warsaw
Refactor by moving mailinglists_helper.new_person ->
68
    >>> anne = factory.makePersonByName('Anne')
6130.6.3 by Maris Fogels
Rewrote the team join pages and the mailing list doctests to take the new behaviour into account.
69
    >>> login(ANONYMOUS)
4897.4.11 by Barry Warsaw
By generalizing the mailing lists test helper module away from just XMLRPC, we
70
    >>> list_set.new(anne, anne)
4419.6.3 by Barry Warsaw
More fleshing out of the mailing list database classes and doctest.
71
    Traceback (most recent call last):
72
    ...
73
    AssertionError: Cannot register a list for a person who is not a team
4419.6.1 by Barry Warsaw
Fleshed out the interface for IMailingList and IMailingListRegistry. Added
74
9965.7.2 by Barry Warsaw
Get rid of the top-level +mailinglists url and fix the test failure fallout.
75
The mailing list registrant must be a team owner or administrator.  Anne is
4543.2.14 by Barry Warsaw
Last round of response to Kiko's review.
76
neither and thus may not create a list for team_two.
77
4897.4.11 by Barry Warsaw
By generalizing the mailing lists test helper module away from just XMLRPC, we
78
    >>> list_set.new(team_two, anne)
4543.2.14 by Barry Warsaw
Last round of response to Kiko's review.
79
    Traceback (most recent call last):
80
    ...
81
    AssertionError: registrant is not a team owner or administrator
82
9965.7.2 by Barry Warsaw
Get rid of the top-level +mailinglists url and fix the test failure fallout.
83
However, if we make Anne a team owner, she can create the mailing list.
4543.2.14 by Barry Warsaw
Last round of response to Kiko's review.
84
11692.6.2 by Curtis Hovey
Use deglober to fixing simple glob imports in doctests.
85
    >>> from lp.registry.interfaces.teammembership import TeamMembershipStatus
6130.6.3 by Maris Fogels
Rewrote the team join pages and the mailing list doctests to take the new behaviour into account.
86
    >>> login('foo.bar@canonical.com')
7315.6.1 by Barry Warsaw
Refactor by moving mailinglists_helper.new_person ->
87
    >>> bart = factory.makePersonByName('Bart')
10002.2.27 by Edwin Grubbs
Simplistic changes to ignore new return values for addMember() and setStatus().
88
    >>> ignored = team_two.addMember(
89
    ...     anne, bart, status=TeamMembershipStatus.ADMIN)
5126.3.25 by Edwin Grubbs
Fixed test failures
90
    >>> login(ANONYMOUS)
4897.4.11 by Barry Warsaw
By generalizing the mailing lists test helper module away from just XMLRPC, we
91
    >>> list_two = list_set.new(team_two, anne)
4543.2.14 by Barry Warsaw
Last round of response to Kiko's review.
92
    >>> list_two
12043.6.3 by Curtis Hovey
Add address to mailing list repr.
93
    <MailingList for team "team-two"; status=APPROVED; ...>
9965.7.2 by Barry Warsaw
Get rid of the top-level +mailinglists url and fix the test failure fallout.
94
    >>> print list_two.address
95
    team-two@lists.launchpad.dev
4543.2.14 by Barry Warsaw
Last round of response to Kiko's review.
96
4419.6.1 by Barry Warsaw
Fleshed out the interface for IMailingList and IMailingListRegistry. Added
97
The newly registered mailing list is linked to its team, and the list's
98
registrant is the team owner at the time the list was registered.  The list's
9965.7.2 by Barry Warsaw
Get rid of the top-level +mailinglists url and fix the test failure fallout.
99
status is set to APPROVED.
100
101
    >>> print list_one.team.displayname
102
    Team One
103
    >>> print list_one.registrant.name
104
    no-priv
105
    >>> print list_one.status.name
106
    APPROVED
107
108
The mailing list has no activation date or welcome message text yet.
109
4419.6.3 by Barry Warsaw
More fleshing out of the mailing list database classes and doctest.
110
    >>> print list_one.date_activated
111
    None
4419.6.7 by Barry Warsaw
Resolve the XXX about communicating welcome message changes to Mailman thusly:
112
    >>> print list_one.welcome_message
4419.6.3 by Barry Warsaw
More fleshing out of the mailing list database classes and doctest.
113
    None
4419.6.1 by Barry Warsaw
Fleshed out the interface for IMailingList and IMailingListRegistry. Added
114
115
A mailing list cannot be registered more than once.
116
4543.2.14 by Barry Warsaw
Last round of response to Kiko's review.
117
    >>> list_set.new(team_one)
4419.6.3 by Barry Warsaw
More fleshing out of the mailing list database classes and doctest.
118
    Traceback (most recent call last):
119
    ...
120
    AssertionError: Mailing list for team "team-one" already exists
4419.6.1 by Barry Warsaw
Fleshed out the interface for IMailingList and IMailingListRegistry. Added
121
122
9965.7.2 by Barry Warsaw
Get rid of the top-level +mailinglists url and fix the test failure fallout.
123
Constructing mailing lists
124
==========================
4419.6.1 by Barry Warsaw
Fleshed out the interface for IMailingList and IMailingListRegistry. Added
125
126
Once a team mailing list has been approved, it can be constructed by Mailman.
127
This happens by returning the set of approved mailing lists through the XMLRPC
128
interface used by Mailman (not shown here).  When Mailman retrieves the set of
129
mailing lists to construct, the list's statuses are set to the CONSTRUCTING
130
state.
131
4543.2.14 by Barry Warsaw
Last round of response to Kiko's review.
132
    >>> sorted_lists(list_set.approved_lists)
12043.6.3 by Curtis Hovey
Add address to mailing list repr.
133
    [<MailingList for team "team-one"; status=APPROVED; ...>,
134
     <MailingList for team "team-two"; status=APPROVED; ...>]
4419.6.3 by Barry Warsaw
More fleshing out of the mailing list database classes and doctest.
135
4543.2.14 by Barry Warsaw
Last round of response to Kiko's review.
136
    >>> list_one.startConstructing()
9965.7.2 by Barry Warsaw
Get rid of the top-level +mailinglists url and fix the test failure fallout.
137
    >>> print list_one.status.name
138
    CONSTRUCTING
4419.6.1 by Barry Warsaw
Fleshed out the interface for IMailingList and IMailingListRegistry. Added
139
140
Once in the construction phase, a list is no longer in the approval state.
141
4543.2.14 by Barry Warsaw
Last round of response to Kiko's review.
142
    >>> sorted_lists(list_set.approved_lists)
12043.6.3 by Curtis Hovey
Add address to mailing list repr.
143
    [<MailingList for team "team-two"; status=APPROVED; ...>]
4419.6.1 by Barry Warsaw
Fleshed out the interface for IMailingList and IMailingListRegistry. Added
144
145
Lists should never be constructed more than once.
146
4543.2.14 by Barry Warsaw
Last round of response to Kiko's review.
147
    >>> list_one.startConstructing()
4419.6.3 by Barry Warsaw
More fleshing out of the mailing list database classes and doctest.
148
    Traceback (most recent call last):
149
    ...
150
    AssertionError: Only approved mailing lists may be constructed
4419.6.1 by Barry Warsaw
Fleshed out the interface for IMailingList and IMailingListRegistry. Added
151
9965.7.2 by Barry Warsaw
Get rid of the top-level +mailinglists url and fix the test failure fallout.
152
Construct another list for later.
153
154
    >>> list_two.startConstructing()
155
156
157
Reporting the results of construction
158
=====================================
4419.6.1 by Barry Warsaw
Fleshed out the interface for IMailingList and IMailingListRegistry. Added
159
160
After Mailman has worked at constructing lists for a while, it reports (again
161
through XMLRPC not shown here) on the status of each list construction.  Most,
4813.11.8 by Guilherme Salgado
Rewrite lots of things to be in accordance with mpt's design
162
if not all will succeed, thus activating the team's mailing list. Also, once
163
a mailing list is made active, its email address is registered in Launchpad
164
and associated with the team's mailing list, so that it can be used as the
165
team's contact address.
4419.6.1 by Barry Warsaw
Fleshed out the interface for IMailingList and IMailingListRegistry. Added
166
14538.1.2 by Curtis Hovey
Moved account and email address to lp.services.identity.
167
    >>> from lp.services.identity.interfaces.emailaddress import (
12043.6.3 by Curtis Hovey
Add address to mailing list repr.
168
    ...     IEmailAddressSet)
4813.11.8 by Guilherme Salgado
Rewrite lots of things to be in accordance with mpt's design
169
    >>> email_set = getUtility(IEmailAddressSet)
170
    >>> print email_set.getByEmail(list_one.address)
171
    None
5711.1.22 by Maris Fogels
Doctest style cleanup.
172
    >>> print list_one.date_activated
173
    None
5711.1.12 by Maris Fogels
Added missing tests and functionality for the date_reviewed and date_activated attributes of MailingList.
174
9965.7.2 by Barry Warsaw
Get rid of the top-level +mailinglists url and fix the test failure fallout.
175
    >>> from lp.registry.interfaces.mailinglist import MailingListStatus
4543.2.16 by Barry Warsaw
On Kiko's suggestion, transitionState() -> transitionToStatus() for
176
    >>> list_one.transitionToStatus(MailingListStatus.ACTIVE)
9965.7.2 by Barry Warsaw
Get rid of the top-level +mailinglists url and fix the test failure fallout.
177
    >>> print list_one.status.name
178
    ACTIVE
179
    >>> print email_set.getByEmail(list_one.address).status.name
180
    VALIDATED
181
    >>> from canonical.database.sqlbase import get_transaction_timestamp
182
    >>> transaction_timestamp = get_transaction_timestamp()
5821.6.28 by James Henstridge
Fix some tests that depended on UTC_NOW not being converted to a real
183
    >>> list_one.date_activated == transaction_timestamp
184
    True
5711.1.12 by Maris Fogels
Added missing tests and functionality for the date_reviewed and date_activated attributes of MailingList.
185
4419.6.1 by Barry Warsaw
Fleshed out the interface for IMailingList and IMailingListRegistry. Added
186
Some list constructions may fail.
187
9965.7.2 by Barry Warsaw
Get rid of the top-level +mailinglists url and fix the test failure fallout.
188
    >>> list_two.transitionToStatus(MailingListStatus.FAILED)
189
    >>> print list_two.status.name
190
    FAILED
4419.6.1 by Barry Warsaw
Fleshed out the interface for IMailingList and IMailingListRegistry. Added
191
4419.6.11 by Barry Warsaw
Change getTeamMailingList() to now take a team name instead of an IPerson
192
You can then get the mailing list for a team, given the team name.
193
4543.2.14 by Barry Warsaw
Last round of response to Kiko's review.
194
    >>> list_set.get(team_one.name)
12043.6.3 by Curtis Hovey
Add address to mailing list repr.
195
    <MailingList for team "team-one"; status=ACTIVE; ...>
4543.2.14 by Barry Warsaw
Last round of response to Kiko's review.
196
    >>> list_set.get(team_two.name)
12043.6.3 by Curtis Hovey
Add address to mailing list repr.
197
    <MailingList for team "team-two"; status=FAILED; ...>
4419.6.11 by Barry Warsaw
Change getTeamMailingList() to now take a team name instead of an IPerson
198
199
This method will return None for missing teams or non-team people.
200
4543.2.14 by Barry Warsaw
Last round of response to Kiko's review.
201
    >>> print list_set.get('not an existing team')
4419.6.11 by Barry Warsaw
Change getTeamMailingList() to now take a team name instead of an IPerson
202
    None
9965.7.2 by Barry Warsaw
Get rid of the top-level +mailinglists url and fix the test failure fallout.
203
    >>> print list_set.get('salgado')
4419.6.11 by Barry Warsaw
Change getTeamMailingList() to now take a team name instead of an IPerson
204
    None
205
4419.6.1 by Barry Warsaw
Fleshed out the interface for IMailingList and IMailingListRegistry. Added
206
9965.7.2 by Barry Warsaw
Get rid of the top-level +mailinglists url and fix the test failure fallout.
207
Deactivating lists
208
==================
4419.6.1 by Barry Warsaw
Fleshed out the interface for IMailingList and IMailingListRegistry. Added
209
210
A list which is active may be deactivated.
211
9965.7.2 by Barry Warsaw
Get rid of the top-level +mailinglists url and fix the test failure fallout.
212
    >>> team_three = new_team('team-three')
213
    >>> list_three = list_set.new(team_three)
214
    >>> list_three.startConstructing()
215
    >>> list_three.transitionToStatus(MailingListStatus.ACTIVE)
216
    >>> transaction.commit()
217
218
    >>> login_person(team_three.teamowner)
4419.6.3 by Barry Warsaw
More fleshing out of the mailing list database classes and doctest.
219
    >>> list_three.deactivate()
9965.7.2 by Barry Warsaw
Get rid of the top-level +mailinglists url and fix the test failure fallout.
220
    >>> print list_three.status.name
221
    DEACTIVATING
4419.6.9 by Barry Warsaw
Handle the situation where a list has been requested for deactivation, and
222
223
This doesn't immediately deactivate the mailing list though.  Mailman still
224
needs to query for the requested deactivations, take the necessary actions,
225
and report the deactivation results.
226
4543.2.14 by Barry Warsaw
Last round of response to Kiko's review.
227
    >>> sorted_lists(list_set.deactivated_lists)
12043.6.3 by Curtis Hovey
Add address to mailing list repr.
228
    [<MailingList for team "team-three"; status=DEACTIVATING; ...>]
4543.2.16 by Barry Warsaw
On Kiko's suggestion, transitionState() -> transitionToStatus() for
229
    >>> list_three.transitionToStatus(MailingListStatus.INACTIVE)
9965.7.2 by Barry Warsaw
Get rid of the top-level +mailinglists url and fix the test failure fallout.
230
    >>> print list_three.status.name
231
    INACTIVE
232
233
Once a list's deactivation is complete, the status of its email address is set
234
to NEW.
235
236
    >>> print email_set.getByEmail(list_three.address).status.name
237
    NEW
4813.11.16 by Guilherme Salgado
Some final changes suggested by Barry
238
4419.6.1 by Barry Warsaw
Fleshed out the interface for IMailingList and IMailingListRegistry. Added
239
But lists which are not active may not be deactivated.
240
9965.7.2 by Barry Warsaw
Get rid of the top-level +mailinglists url and fix the test failure fallout.
241
    >>> list_three.deactivate()
242
    Traceback (most recent call last):
243
    ...
244
    AssertionError: Only active mailing lists may be deactivated
4419.6.1 by Barry Warsaw
Fleshed out the interface for IMailingList and IMailingListRegistry. Added
245
    >>> list_two.deactivate()
4419.6.3 by Barry Warsaw
More fleshing out of the mailing list database classes and doctest.
246
    Traceback (most recent call last):
247
    ...
248
    AssertionError: Only active mailing lists may be deactivated
9965.7.2 by Barry Warsaw
Get rid of the top-level +mailinglists url and fix the test failure fallout.
249
250
251
Reactivating lists
252
==================
4927.1.1 by Guilherme Salgado
Add a reactivate() method to IMailingList to allow inactive MLs to be reactivated.
253
254
A list which is inactive may be reactivated.
255
9965.7.2 by Barry Warsaw
Get rid of the top-level +mailinglists url and fix the test failure fallout.
256
    >>> print list_three.status.name
257
    INACTIVE
4927.1.1 by Guilherme Salgado
Add a reactivate() method to IMailingList to allow inactive MLs to be reactivated.
258
    >>> list_three.reactivate()
259
260
This doesn't immediately reactivate the mailing list though.  Mailman still
261
needs to query for the requested reactivations, take the necessary actions,
262
and report the reactivation results.
263
9965.7.2 by Barry Warsaw
Get rid of the top-level +mailinglists url and fix the test failure fallout.
264
    >>> print list_three.status.name
265
    APPROVED
4927.1.1 by Guilherme Salgado
Add a reactivate() method to IMailingList to allow inactive MLs to be reactivated.
266
267
But lists which are not inactive may not be reactivated.
268
269
    >>> list_three.reactivate()
270
    Traceback (most recent call last):
271
    ...
272
    AssertionError: Only inactive mailing lists may be reactivated
273
274
9965.7.2 by Barry Warsaw
Get rid of the top-level +mailinglists url and fix the test failure fallout.
275
Mailing list permissions
276
========================
5084.12.9 by Leonard Richardson
Added a test for the new security check on mailing lists.
277
5127.3.5 by Leonard Richardson
Simplified form, permissions, and field-building code.
278
Permissions on a team's mailing list are not tracked separately from
279
permissions on the team.
5084.12.13 by Leonard Richardson
Still more miscellaneous cleanup.
280
11118.2.2 by Brad Crittenden
Fixed failing tests due to removal of PMTs
281
You cannot turn a team with a mailing list into a private team.
6514.1.1 by Barry Warsaw
Allow private teams to have mailing lists.
282
283
    >>> from zope.component import queryAdapter
284
    >>> from canonical.lazr.interfaces.objectprivacy import IObjectPrivacy
7675.110.3 by Curtis Hovey
Ran the migration script to move registry code to lp.registry.
285
    >>> from lp.registry.interfaces.person import PersonVisibility
6514.1.1 by Barry Warsaw
Allow private teams to have mailing lists.
286
    >>> queryAdapter(team_one, IObjectPrivacy).is_private
287
    False
288
    >>> login('foo.bar@canonical.com')
11118.2.2 by Brad Crittenden
Fixed failing tests due to removal of PMTs
289
    >>> team_one.visibility = PersonVisibility.PRIVATE
6514.1.1 by Barry Warsaw
Allow private teams to have mailing lists.
290
    Traceback (most recent call last):
291
    ...
8620.2.7 by Brad Crittenden
Fixed test errors.
292
    ImmutableVisibilityError: This team cannot be converted to
11118.2.2 by Brad Crittenden
Fixed failing tests due to removal of PMTs
293
    Private since it is referenced by a mailing list.
6514.1.1 by Barry Warsaw
Allow private teams to have mailing lists.
294
11118.2.2 by Brad Crittenden
Fixed failing tests due to removal of PMTs
295
However, you can give a private team a mailing list.
6514.1.1 by Barry Warsaw
Allow private teams to have mailing lists.
296
297
    >>> thereminists = new_team('thereminists')
298
    >>> queryAdapter(thereminists, IObjectPrivacy).is_private
299
    False
11118.2.2 by Brad Crittenden
Fixed failing tests due to removal of PMTs
300
    >>> thereminists.visibility = PersonVisibility.PRIVATE
9965.7.2 by Barry Warsaw
Get rid of the top-level +mailinglists url and fix the test failure fallout.
301
6514.1.1 by Barry Warsaw
Allow private teams to have mailing lists.
302
    >>> queryAdapter(thereminists, IObjectPrivacy).is_private
303
    True
7675.110.3 by Curtis Hovey
Ran the migration script to move registry code to lp.registry.
304
    >>> from lp.registry.tests.mailinglists_helper import (
6514.1.1 by Barry Warsaw
Allow private teams to have mailing lists.
305
    ...     new_list_for_team)
306
    >>> thereminists_list = new_list_for_team(thereminists)
307
    >>> thereminists_list
12043.6.3 by Curtis Hovey
Add address to mailing list repr.
308
    <MailingList for team "thereminists"; status=ACTIVE; ...>
6514.1.1 by Barry Warsaw
Allow private teams to have mailing lists.
309
9965.7.2 by Barry Warsaw
Get rid of the top-level +mailinglists url and fix the test failure fallout.
310
Welcome messages
311
================
4419.6.1 by Barry Warsaw
Fleshed out the interface for IMailingList and IMailingListRegistry. Added
312
313
Mailing lists have a welcome message text which is sent to new members when
4419.6.7 by Barry Warsaw
Resolve the XXX about communicating welcome message changes to Mailman thusly:
314
they subscribe to a list.  The welcome message can contain any text.
4419.6.1 by Barry Warsaw
Fleshed out the interface for IMailingList and IMailingListRegistry. Added
315
4419.6.7 by Barry Warsaw
Resolve the XXX about communicating welcome message changes to Mailman thusly:
316
    >>> print list_one.welcome_message
317
    None
318
    >>> list_one.welcome_message = """\
4419.6.1 by Barry Warsaw
Fleshed out the interface for IMailingList and IMailingListRegistry. Added
319
    ... Welcome to the Team One mailing list."""
5127.3.8 by Leonard Richardson
Perform mailing list tasks when logged in as an authorized user.
320
    >>> login(ANONYMOUS)
9965.7.2 by Barry Warsaw
Get rid of the top-level +mailinglists url and fix the test failure fallout.
321
    >>> print list_one.welcome_message
322
    Welcome to the Team One mailing list.
4419.6.7 by Barry Warsaw
Resolve the XXX about communicating welcome message changes to Mailman thusly:
323
324
After changing the welcome message, the list's status should be MODIFIED.
325
9965.7.2 by Barry Warsaw
Get rid of the top-level +mailinglists url and fix the test failure fallout.
326
    >>> print list_one.status.name
327
    MODIFIED
4543.2.14 by Barry Warsaw
Last round of response to Kiko's review.
328
    >>> sorted_lists(list_set.modified_lists)
12043.6.3 by Curtis Hovey
Add address to mailing list repr.
329
    [<MailingList for team "team-one"; status=MODIFIED; ...>]
4419.6.7 by Barry Warsaw
Resolve the XXX about communicating welcome message changes to Mailman thusly:
330
4483.4.20 by Barry Warsaw
Addressed BjornT's review comments.
331
Eventually, Mailman will get around to acting on this modification.  When it
332
does so, the list's state transitions first to UPDATING so as to avoid
333
multiple modifications.  Transitioning to the ACTIVE state while still
334
MODIFIED is not allowed.
335
336
    >>> list_one.transitionToStatus(MailingListStatus.ACTIVE)
337
    Traceback (most recent call last):
338
    ...
4813.11.8 by Guilherme Salgado
Rewrite lots of things to be in accordance with mpt's design
339
    AssertionError: Not a valid state transition: Modified -> Active
4483.4.20 by Barry Warsaw
Addressed BjornT's review comments.
340
341
What really happens is that the list's state is first transitioned to
342
UPDATING, and then to ACTIVE or FAILED.
343
344
    >>> list_one.startUpdating()
9965.7.2 by Barry Warsaw
Get rid of the top-level +mailinglists url and fix the test failure fallout.
345
    >>> print list_one.status.name
346
    UPDATING
4543.2.16 by Barry Warsaw
On Kiko's suggestion, transitionState() -> transitionToStatus() for
347
    >>> list_one.transitionToStatus(MailingListStatus.ACTIVE)
9965.7.2 by Barry Warsaw
Get rid of the top-level +mailinglists url and fix the test failure fallout.
348
    >>> print list_one.status.name
349
    ACTIVE
4419.6.10 by Barry Warsaw
Allow REGISTERED mailing lists to have their welcome_message text modified.
350
4419.6.7 by Barry Warsaw
Resolve the XXX about communicating welcome message changes to Mailman thusly:
351
You cannot change the welcome message text for a mailing list in anything but
9965.7.2 by Barry Warsaw
Get rid of the top-level +mailinglists url and fix the test failure fallout.
352
the ACTIVE status.
4419.6.7 by Barry Warsaw
Resolve the XXX about communicating welcome message changes to Mailman thusly:
353
5127.3.8 by Leonard Richardson
Perform mailing list tasks when logged in as an authorized user.
354
    >>> login('foo.bar@canonical.com')
4419.6.7 by Barry Warsaw
Resolve the XXX about communicating welcome message changes to Mailman thusly:
355
    >>> list_two.welcome_message = """\
356
    ... This list has been declined."""
357
    Traceback (most recent call last):
358
    ...
9965.7.4 by Barry Warsaw
More test repair.
359
    AssertionError: Only usable mailing lists may be modified
4419.6.7 by Barry Warsaw
Resolve the XXX about communicating welcome message changes to Mailman thusly:
360
9965.7.2 by Barry Warsaw
Get rid of the top-level +mailinglists url and fix the test failure fallout.
361
    >>> list_three.welcome_message = """\
4419.6.7 by Barry Warsaw
Resolve the XXX about communicating welcome message changes to Mailman thusly:
362
    ... This list has been deactivated."""
363
    Traceback (most recent call last):
364
    ...
9965.7.4 by Barry Warsaw
More test repair.
365
    AssertionError: Only usable mailing lists may be modified
5138.8.1 by Barry Warsaw
Implementation of blueprint team-mailing-lists-restrict-team-rename.
366
367
9965.7.2 by Barry Warsaw
Get rid of the top-level +mailinglists url and fix the test failure fallout.
368
Renaming teams with mailing lists
369
=================================
5138.8.1 by Barry Warsaw
Implementation of blueprint team-mailing-lists-restrict-team-rename.
370
371
A team that has a mailing list may not be renamed.
372
373
    >>> login('no-priv@canonical.com')
374
    >>> team_one.name = 'team-canonical'
375
    Traceback (most recent call last):
376
    ...
377
    AssertionError: Cannot rename teams with mailing lists
378
379
But a team with no mailing list (yet) can still be renamed.
380
381
    >>> team_six = new_team('team-six')
382
    >>> team_six.name = 'team-canonical'
9965.7.2 by Barry Warsaw
Get rid of the top-level +mailinglists url and fix the test failure fallout.
383
    >>> print team_six.name
384
    team-canonical
385
386
387
Team archive links
388
==================
5256.1.1 by Barry Warsaw
Implementation and tests for team-mailing-lists-archiving blueprint:
389
5256.1.2 by Barry Warsaw
Changes in response to jml's review:
390
Mailing lists have archives, accessible through a list-specific url.  However,
391
if a mailing list has never be activated, it won't have an archive url.
392
393
    >>> print list_two.archive_url
394
    None
395
396
An active mailing list has an archive url.
5256.1.1 by Barry Warsaw
Implementation and tests for team-mailing-lists-archiving blueprint:
397
9965.7.2 by Barry Warsaw
Get rid of the top-level +mailinglists url and fix the test failure fallout.
398
    >>> print list_one.status.name
399
    ACTIVE
400
    >>> print list_one.archive_url
401
    http://lists.launchpad.dev/team-one
5256.1.2 by Barry Warsaw
Changes in response to jml's review:
402
403
Inactive mailing lists also have an archive url, because once activated, a
404
mailing list could have an archive and archives are never deleted.
405
5256.1.1 by Barry Warsaw
Implementation and tests for team-mailing-lists-archiving blueprint:
406
    >>> list_one.deactivate()
407
    >>> list_one.transitionToStatus(MailingListStatus.INACTIVE)
9965.7.2 by Barry Warsaw
Get rid of the top-level +mailinglists url and fix the test failure fallout.
408
    >>> print list_one.status.name
409
    INACTIVE
410
    >>> print list_one.archive_url
411
    http://lists.launchpad.dev/team-one
412
413
414
Events
415
======
5711.1.1 by Maris Fogels
Added a failing doctest for the mailing list availability event.
416
5711.1.19 by Maris Fogels
Clarified some doctest narratives.
417
Activating the mailing list (changing it's status to 'available for
7876.3.6 by Francis J. Lacoste
Used ISQLObject from lazr.lifecycle
418
subscription') will fire an instance of the ObjectModifiedEvent.
5711.1.1 by Maris Fogels
Added a failing doctest for the mailing list availability event.
419
420
    # Register an event listener that will print event it receives.
14560.2.10 by Curtis Hovey
Removed shim.
421
    >>> from canonical.lazr.testing.event import TestEventListener
7876.3.14 by Francis J. Lacoste
Review comments.
422
    >>> from lazr.lifecycle.interfaces import IObjectModifiedEvent
11692.6.2 by Curtis Hovey
Use deglober to fixing simple glob imports in doctests.
423
    >>> from lp.registry.interfaces.mailinglist import IMailingList
5711.1.1 by Maris Fogels
Added a failing doctest for the mailing list availability event.
424
    >>> def print_event(object, event):
425
    ...     print "Received %s on %s" % (
426
    ...         event.__class__.__name__.split('.')[-1],
427
    ...         object.__class__.__name__.split('.')[-1])
428
    >>> mailinglist_event_listener = TestEventListener(
7876.3.6 by Francis J. Lacoste
Used ISQLObject from lazr.lifecycle
429
    ...     IMailingList, IObjectModifiedEvent, print_event)
5711.1.1 by Maris Fogels
Added a failing doctest for the mailing list availability event.
430
431
432
    # We need to build a new mailing list to use in our tests
433
    >>> list_six = list_set.new(team_six)
434
    >>> list_six.startConstructing()
9965.7.2 by Barry Warsaw
Get rid of the top-level +mailinglists url and fix the test failure fallout.
435
    >>> print list_six.status.name
436
    CONSTRUCTING
5711.1.1 by Maris Fogels
Added a failing doctest for the mailing list availability event.
437
438
    >>> list_six.transitionToStatus(MailingListStatus.ACTIVE)
7876.3.6 by Francis J. Lacoste
Used ISQLObject from lazr.lifecycle
439
    Received ObjectModifiedEvent on MailingList
9965.7.2 by Barry Warsaw
Get rid of the top-level +mailinglists url and fix the test failure fallout.
440
    >>> print list_six.status.name
441
    ACTIVE
5711.1.1 by Maris Fogels
Added a failing doctest for the mailing list availability event.
442
443
    # Cleanup
444
    >>> mailinglist_event_listener.unregister()
6815.1.1 by Barry Warsaw
Add IMailingList.purge() which, while not deleting the mailing list, makes it
445
446
9965.7.2 by Barry Warsaw
Get rid of the top-level +mailinglists url and fix the test failure fallout.
447
Purging
448
=======
6815.1.1 by Barry Warsaw
Add IMailingList.purge() which, while not deleting the mailing list, makes it
449
450
There are times when we want to perform certain actions that normally are
451
unsafe to do when a team has a mailing list.  For example, we might want to
452
merge a team with a mailing list into another team, or we might want to allow
453
a team owner to re-request a mailing list that was incorrectly declined.
454
455
In order to support this, mailing lists have a PURGED state.  Purging a
456
mailing list on the Launchpad side performs no communication with Mailman; the
457
Launchpad administrator must ensure that all associated state is purged from
458
Mailman (which is aided by the use of a script to be run on that server).  On
459
Launchpad, only a Launchpad administrator or mailing list expert may purge a
460
list, and then only if the list is already in one of the safe-to-purge states.
461
462
A list in the active state is not safe to purge.
463
464
    >>> print list_six.status.name
465
    ACTIVE
466
    >>> list_six.purge()
467
    Traceback (most recent call last):
468
    UnsafeToPurge: Cannot purge mailing list in ACTIVE state: team-canonical
469
470
By deactivating the mailing list, we make it safe to purge.
471
7675.85.2 by Jonathan Lange
Undo revision generated by step 2 of process.
472
    >>> # Need to commit, or security checks fail because team isn't yet
473
    >>> # available via the auth Store yet.
474
    >>> import transaction
475
    >>> transaction.commit()
6815.1.1 by Barry Warsaw
Add IMailingList.purge() which, while not deleting the mailing list, makes it
476
    >>> list_six.deactivate()
477
    >>> list_six.transitionToStatus(MailingListStatus.INACTIVE)
478
    >>> print list_six.status.name
479
    INACTIVE
480
    >>> list_six.purge()
481
    >>> print list_six.status.name
482
    PURGED
483
484
It's as if the mailing list never existed, so we can re-request that the list
485
be created.
486
487
    >>> list_six = list_set.new(team_six)
488
    >>> print list_six.team.name
489
    team-canonical
490
    >>> print list_six.date_activated
491
    None
492
    >>> print list_six.status.name
9965.7.2 by Barry Warsaw
Get rid of the top-level +mailinglists url and fix the test failure fallout.
493
    APPROVED
6815.1.1 by Barry Warsaw
Add IMailingList.purge() which, while not deleting the mailing list, makes it
494
    >>> print list_six.welcome_message
495
    None
496
9965.7.2 by Barry Warsaw
Get rid of the top-level +mailinglists url and fix the test failure fallout.
497
A list that has been approved, or is being constructed cannot be purged.
6815.1.1 by Barry Warsaw
Add IMailingList.purge() which, while not deleting the mailing list, makes it
498
6821.6.7 by Barry Warsaw
Fixes in response to celso's review.
499
    >>> import transaction
6815.1.1 by Barry Warsaw
Add IMailingList.purge() which, while not deleting the mailing list, makes it
500
    >>> from zope.security.proxy import removeSecurityProxy
6821.6.7 by Barry Warsaw
Fixes in response to celso's review.
501
    >>> naked_list = removeSecurityProxy(list_six)
502
    >>> naked_list.status = MailingListStatus.APPROVED
6815.1.1 by Barry Warsaw
Add IMailingList.purge() which, while not deleting the mailing list, makes it
503
    >>> transaction.commit()
12651.1.14 by Curtis Hovey
No not try to delete the mailing list address if it does not exist.
504
    >>> login(ANONYMOUS)
6815.1.1 by Barry Warsaw
Add IMailingList.purge() which, while not deleting the mailing list, makes it
505
    >>> print list_six.status.name
506
    APPROVED
507
    >>> list_six.purge()
508
    Traceback (most recent call last):
509
    ...
510
    UnsafeToPurge: Cannot purge mailing list in APPROVED state: team-canonical
511
6821.6.7 by Barry Warsaw
Fixes in response to celso's review.
512
    >>> naked_list.status = MailingListStatus.CONSTRUCTING
6815.1.1 by Barry Warsaw
Add IMailingList.purge() which, while not deleting the mailing list, makes it
513
    >>> transaction.commit()
514
    >>> print list_six.status.name
515
    CONSTRUCTING
516
    >>> list_six.purge()
517
    Traceback (most recent call last):
518
    ...
519
    UnsafeToPurge: Cannot purge mailing list in CONSTRUCTING state: ...
520
521
A list in the FAILED state can be purged, but a list in the MOD_FAILED state
522
cannot.  This is because the latter still means that a mailing list is active
523
for the team.
524
6821.6.7 by Barry Warsaw
Fixes in response to celso's review.
525
    >>> naked_list.status = MailingListStatus.FAILED
6815.1.1 by Barry Warsaw
Add IMailingList.purge() which, while not deleting the mailing list, makes it
526
    >>> transaction.commit()
527
    >>> print list_six.status.name
528
    FAILED
529
    >>> list_six.purge()
530
    >>> print list_six.status.name
531
    PURGED
532
6821.6.7 by Barry Warsaw
Fixes in response to celso's review.
533
    >>> naked_list.status = MailingListStatus.MOD_FAILED
6815.1.1 by Barry Warsaw
Add IMailingList.purge() which, while not deleting the mailing list, makes it
534
    >>> transaction.commit()
535
    >>> print list_six.status.name
536
    MOD_FAILED
537
    >>> list_six.purge()
538
    Traceback (most recent call last):
539
    ...
540
    UnsafeToPurge: Cannot purge mailing list in MOD_FAILED state: ...
541
542
Modified, updating, and deactivating mailing lists are also unsafe to purge.
543
6821.6.7 by Barry Warsaw
Fixes in response to celso's review.
544
    >>> naked_list.status = MailingListStatus.MODIFIED
6815.1.1 by Barry Warsaw
Add IMailingList.purge() which, while not deleting the mailing list, makes it
545
    >>> transaction.commit()
546
    >>> print list_six.status.name
547
    MODIFIED
548
    >>> list_six.purge()
549
    Traceback (most recent call last):
550
    ...
551
    UnsafeToPurge: Cannot purge mailing list in MODIFIED state: team-canonical
552
6821.6.7 by Barry Warsaw
Fixes in response to celso's review.
553
    >>> naked_list.status = MailingListStatus.UPDATING
6815.1.1 by Barry Warsaw
Add IMailingList.purge() which, while not deleting the mailing list, makes it
554
    >>> transaction.commit()
555
    >>> print list_six.status.name
556
    UPDATING
557
    >>> list_six.purge()
558
    Traceback (most recent call last):
559
    ...
560
    UnsafeToPurge: Cannot purge mailing list in UPDATING state: team-canonical
561
6821.6.7 by Barry Warsaw
Fixes in response to celso's review.
562
    >>> naked_list.status = MailingListStatus.DEACTIVATING
6815.1.1 by Barry Warsaw
Add IMailingList.purge() which, while not deleting the mailing list, makes it
563
    >>> transaction.commit()
564
    >>> print list_six.status.name
565
    DEACTIVATING
566
    >>> list_six.purge()
567
    Traceback (most recent call last):
568
    ...
569
    UnsafeToPurge: Cannot purge mailing list in DEACTIVATING state: ...
570
571
You should never be able to purge an already purged mailing list.
572
6821.6.7 by Barry Warsaw
Fixes in response to celso's review.
573
    >>> naked_list.status = MailingListStatus.PURGED
6815.1.1 by Barry Warsaw
Add IMailingList.purge() which, while not deleting the mailing list, makes it
574
    >>> transaction.commit()
575
    >>> print list_six.status.name
576
    PURGED
577
    >>> list_six.purge()
578
    Traceback (most recent call last):
579
    ...
580
    AssertionError: Already purged