~launchpad-pqm/launchpad/devel

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
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
Team pages
==========

Home page
---------

    >>> from zope.component import getMultiAdapter
    >>> from lp.registry.interfaces.person import IPersonSet
    >>> from lp.registry.interfaces.teammembership import ITeamMembershipSet
    >>> person_set = getUtility(IPersonSet)

On a team home page, we show up to the latest five members who were approved
as well as the up to five latest members who proposed themselves.

    >>> ubuntu_team = person_set.getByName('name18')
    >>> team_home = create_initialized_view(ubuntu_team, '+index')
    >>> membershipset = getUtility(ITeamMembershipSet)
    >>> for member in team_home.recently_approved_members:
    ...     membership = membershipset.getByPersonAndTeam(member, ubuntu_team)
    ...     print "%s: %s" % (member.name, membership.status.title)
    name20: Approved
    spiv: Approved
    limi: Approved

    >>> for member in team_home.recently_proposed_members:
    ...     membership = membershipset.getByPersonAndTeam(member, ubuntu_team)
    ...     print "%s: %s" % (member.name, membership.status.title)
    justdave: Proposed

If new members are added/proposed, they'll show up at the top of the lists.

    >>> sample_person = person_set.getByName('name12')
    >>> login_person(sample_person)
    >>> sample_person.join(ubuntu_team)
    >>> salgado = person_set.getByName('salgado')
    >>> mark = person_set.getByName('mark')
    >>> login('foo.bar@canonical.com')
    >>> ignored = ubuntu_team.addMember(salgado, reviewer=mark)

    >>> team_home = create_initialized_view(ubuntu_team, '+index')
    >>> for member in team_home.recently_approved_members:
    ...     membership = membershipset.getByPersonAndTeam(member, ubuntu_team)
    ...     print "%s: %s" % (member.name, membership.status.title)
    salgado: Approved
    name20: Approved
    spiv: Approved
    limi: Approved

    >>> for member in team_home.recently_proposed_members:
    ...     membership = membershipset.getByPersonAndTeam(member, ubuntu_team)
    ...     print "%s: %s" % (member.name, membership.status.title)
    name12: Proposed
    justdave: Proposed

Posting malformed data to the team home page raises an error.

    >>> from canonical.launchpad.webapp.servers import LaunchpadTestRequest
    >>> broken_request = LaunchpadTestRequest(
    ...     form={})
    >>> broken_request.method = 'POST'
    >>> team_home = getMultiAdapter(
    ...     (ubuntu_team, broken_request), name='+index')
    >>> team_home.initialize()
    Traceback (most recent call last):
    ...
    UnexpectedFormData: The mailing list form did not receive the expected
    form fields.


+map-portlet
------------

The team profile page contain the location portlet that shows a map.

    >>> team_view = create_initialized_view(ubuntu_team, '+index')
    >>> team_view.has_visible_location
    False

After a member has set his location, the map will be rendered.

    >>> login_person(sample_person)
    >>> sample_person.setLocation(
    ...     38.81, 77.1, 'America/New_York', sample_person)

    >>> team_view = create_initialized_view(ubuntu_team, '+index')
    >>> team_view.has_visible_location
    True

The small_maps key in the launchpad_views cookie can be set by the viewing
user to 'false' to indicate that small maps are not wanted.

    >>> cookie = 'launchpad_views=small_maps=false'
    >>> team_view = create_initialized_view(
    ...     ubuntu_team, '+index', cookie=cookie)

+map
----

A team's +map page will show a map with markers on the location of all
members of that team.  That page also lists the local times of members.

    >>> login('foo.bar@canonical.com')
    >>> context = factory.makeTeam(salgado)
    >>> context.mapped_participants_count
    0
    >>> view = create_initialized_view(context, '+map')
    >>> view.times
    []


Once a member is mapped, the map will be rendered. The view provides a cached
property to access the mapped participants. The views number of times is
incremented for each timezone the members reside in.

    >>> london_member = factory.makePerson(
    ...     latitude=51.49, longitude=-0.13, time_zone='Europe/London')
    >>> ignored = context.addMember(london_member, mark)
    >>> context.mapped_participants_count
    1
    >>> view = create_initialized_view(context, '+map')
    >>> len(view.mapped_participants)
    1
    >>> len(view.times)
    1

    >>> brazil_member = factory.makePerson(
    ...     latitude=-23.60, longitude=-46.64, time_zone='America/Sao_Paulo')
    >>> ignored = context.addMember(brazil_member, mark)
    >>> context.mapped_participants_count
    2
    >>> view = create_initialized_view(context, '+map')
    >>> len(view.mapped_participants)
    2
    >>> len(view.times)
    2

If we had two members in London, though, we wouldn't list London's time
twice, for obvious reasons.

    >>> london_member2 = factory.makePerson(
    ...     latitude=51.49, longitude=-0.13, time_zone='Europe/London')
    >>> ignored = context.addMember(london_member2, mark)
    >>> context.mapped_participants_count
    3
    >>> view = create_initialized_view(context, '+map')
    >>> len(view.times)
    2

The number of persons returned by mapped_participants is governed by the
view's limit attribute. The default value is None, meaning there is no
limit.  This view is used for displaying the full map.

    >>> print view.limit
    None
    >>> len(view.mapped_participants)
    3

A portlet for the map also exists which is used for displaying the map
inside the person or team page.  It has the number of participants limited.

    >>> view = create_initialized_view(context, '+portlet-map')
    >>> view.limit
    24

    # Hack the limit to demonstrate that it controls mapped_participants.
    >>> view.limit = 1
    >>> len(view.mapped_participants)
    1

The view provides the count of mapped and unmapped members. The counts
are cached to improve performance.

    >>> view = create_initialized_view(context, '+map')
    >>> view.mapped_participants_count
    3
    >>> view.unmapped_participants_count
    1

The template can ask if the team has mapped members, so that it does not
need to work with counts or the list of members.

    >>> view.has_mapped_participants
    True

The cached bounds of the mapped members of the team can be used to define
the scale of a map.

    >>> bounds = view.bounds
    >>> for key in sorted(bounds):
    ...     print "%s: %s" % (key, bounds[key])
    center_lat: 13...
    center_lng: -23...
    max_lat: 51...
    max_lng: -0...
    min_lat: -23...
    min_lng: -46...


Contacting the team
-------------------

Logged in users can contact any other Launchpad user through a 'Contact this
user' feature (see person-pages.txt).  Similarly, a logged in user can contact
a team through a 'Contact this team' link.

The team's overview page tells us that we're contacting a team instead of a
user. The title of the link states that the team owner will be contacted
because bart is not a member of the team.

    >>> guadamen = person_set.getByName('guadamen')
    >>> bart = factory.makePerson(email='bart@example.com', name='bart')
    >>> login_person(bart)

    >>> view = create_initialized_view(guadamen, '+index')
    >>> print view.contact_link_title
    Send an email to this team's owner through Launchpad
    >>> print view.specific_contact_text
    Contact this team's owner


Mugshots
--------

The mugshots for all members of a team can be seen on the +mugshots
page.  The display of mugshots is batched.

    >>> ubuntu = person_set.getByName('ubuntu-team')
    >>> view = create_initialized_view(ubuntu, '+mugshots')
    >>> batch = view.members.currentBatch()
    >>> print len(list(ubuntu.allmembers))
    10
    >>> print view.batch_size
    8
    >>> print len(list(batch))
    8
    >>> from zope.security.proxy import removeSecurityProxy
    >>> for person in list(batch):
    ...     print removeSecurityProxy(person)
    <Person at ... limi (Alexander Limi)>
    <Person at ... cprov (Celso Providelo)>
    <Person at ... kamion (Colin Watson)>
    <Person at ... kinnison (Daniel Silverstone)>
    <Person at ... edgar (Edgar Bursic)>
    <Person at ... name16 (Foo Bar)>
    <Person at ... jdub (Jeff Waugh)>
    <Person at ... mark (Mark Shuttleworth)>


Privacy and visibility
----------------------

Team visibility is used on the page to indicate privacy.  GuadaMen is a public
team.

    >>> view = create_initialized_view(guadamen, '+index')
    >>> print view.visibility_info
    Public team
    >>> print view.visibility_portlet_class
    portlet

Private teams are indicated as private.

    >>> from lp.registry.interfaces.person import PersonVisibility
    >>> login('admin@canonical.com')
    >>> private_team = factory.makeTeam(
    ...     owner=sample_person,
    ...     name='private-team', displayname='Private Team',
    ...     visibility=PersonVisibility.PRIVATE)

    >>> view = create_initialized_view(private_team, '+index')
    >>> print view.visibility_info
    Private team
    >>> print view.visibility_portlet_class
    portlet private

+add-my-teams
-------------

This page lists the teams that you administer and can add as a member
to the current team. Private teams cannot be added as members to another
team.

    >>> login_person(sample_person)
    >>> view = create_initialized_view(guadamen, '+add-my-teams')
    >>> for candidate in view.candidate_teams:
    ...     print candidate.name, candidate.visibility.title
    landscape-developers Public
    launchpad-users Public