1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
|
People Merge Pages
==================
There are a number of views for merging people and teams.
Team Merges
-----------
Create a member of the registry team that is not a member of the admins
team.
>>> from lp.app.interfaces.launchpad import ILaunchpadCelebrities
>>> from lp.registry.interfaces.person import IPersonSet
>>> registry_experts = getUtility(ILaunchpadCelebrities).registry_experts
>>> person_set = getUtility(IPersonSet)
>>> registry_expert= factory.makeRegistryExpert()
>>> login_person(registry_expert)
A team (name21) can be merged into another (ubuntu-team).
>>> print person_set.getByName('name21').displayname
Hoary Gnome Team
>>> print person_set.getByName('ubuntu-team').displayname
Ubuntu Team
>>> form = {'field.dupe_person': 'name21',
... 'field.target_person': 'ubuntu-team',
... 'field.actions.merge': 'Merge'}
>>> view = create_initialized_view(
... person_set, '+adminteammerge', form=form)
>>> len(view.errors)
0
>>> for notification in view.request.response.notifications:
... print notification.message
A merge is queued and is expected to complete in a few minutes.
Attempting to merge a non-existent team results in an error.
>>> transaction.commit()
>>> form = {'field.dupe_person': 'not-a-real-team',
... 'field.target_person': 'another-fake-team',
... 'field.actions.merge': 'Merge'}
>>> view = create_initialized_view(
... person_set, '+adminteammerge', form=form)
>>> len(view.errors)
2
>>> for error in view.errors:
... print error[0]
Invalid value
Invalid value
People merge
------------
For regular users to merge Launchpad profiles, we require that they confirm
ownership of the merged profile's email addresses, so it's not possible for
them to merge a profile which has no email address.
>>> admin = person_set.getByName('name16')
>>> login_person(registry_expert)
# First we need to forge a person without a single email address.
>>> from lp.registry.interfaces.person import PersonCreationRationale
>>> person = person_set.createPersonWithoutEmail(
... 'email-less-person', PersonCreationRationale.UNKNOWN)
>>> transaction.commit()
>>> form = {'field.dupe_person': person.name,
... 'field.actions.continue': 'Continue'}
>>> view = create_initialized_view(
... person_set, '+requestmerge', form=form)
>>> len(view.errors)
2
>>> print view.getFieldError('dupe_person')
The duplicate is not a valid person or team.
Admins, on the other hand, are allowed to merge people without a single email
address.
>>> form = {'field.dupe_person': person.name,
... 'field.target_person': 'name16',
... 'field.actions.merge': 'Merge'}
>>> view = create_initialized_view(
... person_set, '+adminpeoplemerge', form=form)
>>> view.errors
[]
>>> print view.request.response.getHeader('location')
http://launchpad.dev/~name16
Delete team
-----------
Users with launchpad.Moderate such as team admins and registry experts
can delete teams.
>>> from canonical.launchpad.webapp.authorization import check_permission
>>> team_owner = factory.makePerson()
>>> team_member = factory.makePerson()
>>> deletable_team = factory.makeTeam(owner=team_owner, name='deletable')
>>> login_person(team_owner)
>>> ignore = deletable_team.addMember(team_member, reviewer=team_owner)
>>> view = create_initialized_view(deletable_team, '+delete')
>>> login_person(team_member)
>>> check_permission('launchpad.Moderate', view)
False
>>> login_person(registry_expert)
>>> check_permission('launchpad.Moderate', view)
True
>>> login_person(team_owner)
>>> check_permission('launchpad.Moderate', view)
True
The view provides a label, page_title, and cancel url to present the page.
>>> print view.label
Delete Deletable
>>> print view.page_title
Delete
>>> print view.cancel_url
http://launchpad.dev/~deletable
The view uses the same form fields as the team merge view, but it does not
render them because their values are preset. Only the action is rendered,
and it is only rendered if canDelete() returns True.
>>> from canonical.launchpad.testing.pages import find_tag_by_id
>>> view = create_initialized_view(
... deletable_team, '+delete', principal=team_owner)
>>> view.field_names
['dupe_person', 'target_person']
>>> view.default_values
{'field.target_person': u'registry', 'field.dupe_person': u'deletable'}
>>> content = find_tag_by_id(view.render(), 'maincontent')
>>> print find_tag_by_id(content, 'field.dupe_person')
None
>>> print find_tag_by_id(content, 'field.target_person')
None
>>> print find_tag_by_id(content, 'field.actions.delete')['value']
Delete
>>> view.canDelete(data={})
True
The page explains how many users will be removed from the team before it is
deleted.
>>> from canonical.launchpad.testing.pages import extract_text
>>> print extract_text(content)
Delete Deletable
Deleting a team is permanent. It cannot be undone.
Deletable has 2 active members who will be removed before it is deleted...
The user is redirected to /people when a team is deleted.
>>> form = {'field.actions.delete': 'Delete'}
>>> view = create_initialized_view(deletable_team, '+delete', form=form)
>>> view.errors
[]
>>> for notification in view.request.response.notifications:
... print notification.message
The team is queued to be deleted.
>>> print view.next_url
http://launchpad.dev/people
The delete team view cannot be hacked to delete another team, or change
the team that the merge operation is performed with.
>>> deletable_team = factory.makeTeam(owner=team_owner, name='delete-me')
>>> transaction.commit()
>>> form = {
... 'field.target_person': 'rosetta-admins',
... 'field.dupe_person': 'landscape-developers',
... 'field.actions.delete': 'Delete'}
>>> view = create_initialized_view(deletable_team, '+delete', form=form)
>>> for error in view.errors:
... print error
Unable to process submitted data.
>>> view.request.response.notifications
[]
A team with a mailing list cannot be deleted.
>>> team, mailing_list = factory.makeTeamAndMailingList(
... 'not-deletable', 'rock')
>>> login_person(team.teamowner)
>>> view = create_initialized_view(
... team, '+delete', principal=team.teamowner)
>>> view.canDelete(data={})
False
>>> view.has_mailing_list
True
>>> content = find_tag_by_id(view.render(), 'maincontent')
>>> print extract_text(content)
Delete Not Deletable
Deleting a team is permanent. It cannot be undone.
This team cannot be deleted until its mailing list is first deactivated,
then purged after the deactivation is confirmed...
>>> print find_tag_by_id(content, 'field.actions.delete')
None
Private teams can be deleted by admins.
>>> from lp.registry.interfaces.person import PersonVisibility
>>> login('commercial-member@canonical.com')
>>> private_team = factory.makeTeam(
... name='secret', visibility=PersonVisibility.PRIVATE)
>>> login('admin@canonical.com')
>>> form = {'field.actions.delete': 'Delete'}
>>> view = create_initialized_view(private_team, '+delete', form=form)
>>> view.errors
[]
>>> for notification in view.request.response.notifications:
... print notification.message
The team is queued to be deleted.
|