Merge lp:~salgado/launchpad/remove-map-views into lp:launchpad
- remove-map-views
- Merge into devel
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Ian Booth | ||||
Approved revision: | no longer in the source branch. | ||||
Merged at revision: | 14834 | ||||
Proposed branch: | lp:~salgado/launchpad/remove-map-views | ||||
Merge into: | lp:launchpad | ||||
Diff against target: |
1945 lines (+95/-1416) 26 files modified
lib/lp/app/widgets/doc/location-widget.txt (+0/-204) lib/lp/app/widgets/location.py (+0/-192) lib/lp/app/widgets/templates/location.pt (+0/-2) lib/lp/blueprints/browser/tests/sprintattendance-views.txt (+0/-20) lib/lp/registry/browser/configure.zcml (+1/-28) lib/lp/registry/browser/person.py (+21/-71) lib/lp/registry/browser/team.py (+0/-116) lib/lp/registry/browser/tests/team-views.txt (+0/-129) lib/lp/registry/doc/personlocation.txt (+17/-195) lib/lp/registry/interfaces/location.py (+12/-6) lib/lp/registry/interfaces/person.py (+1/-35) lib/lp/registry/model/person.py (+18/-113) lib/lp/registry/stories/location/personlocation-edit.txt (+7/-28) lib/lp/registry/stories/location/personlocation.txt (+0/-18) lib/lp/registry/stories/location/team-map.txt (+0/-86) lib/lp/registry/stories/person/xx-person-home.txt (+5/-20) lib/lp/registry/stories/webservice/xx-personlocation.txt (+9/-25) lib/lp/registry/templates/person-index.pt (+0/-2) lib/lp/registry/templates/person-portlet-contact-details.pt (+1/-1) lib/lp/registry/templates/person-portlet-map.pt (+0/-21) lib/lp/registry/templates/team-map-data.pt (+0/-17) lib/lp/registry/templates/team-map.pt (+0/-45) lib/lp/registry/templates/team-portlet-map.pt (+0/-37) lib/lp/registry/tests/test_person.py (+1/-2) lib/lp/registry/tests/test_personset.py (+1/-2) lib/lp/registry/xmlrpc/canonicalsso.py (+1/-1) |
||||
To merge this branch: | bzr merge lp:~salgado/launchpad/remove-map-views | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ian Booth (community) | Approve | ||
Review via email:
|
Commit message
[r=wallyworld][bug=933911] Remove most of the code related to people's locations. We're not able to remove everything yet because the time zone is still stored in PersonLocation.
Description of the change
This removes most of the code related to people's locations. We're not able to remove everything yet because the time zone is still stored in PersonLocation, but I've filed a bug for that.
As per IRC discussion with Rob, I've unexported the location-related bits on the devel API and made them always return None for other versions. I wanted to get rid of Person.location and Person.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Guilherme Salgado (salgado) wrote : | # |
Thanks for the review, Ian. I've linked a bug to my branch
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Guilherme Salgado (salgado) wrote : | # |
There was a test which was still using setLocationVisi
Preview Diff
1 | === removed file 'lib/lp/app/widgets/doc/location-widget.txt' |
2 | --- lib/lp/app/widgets/doc/location-widget.txt 2012-02-02 12:30:53 +0000 |
3 | +++ lib/lp/app/widgets/doc/location-widget.txt 1970-01-01 00:00:00 +0000 |
4 | @@ -1,204 +0,0 @@ |
5 | -Location widget |
6 | -=============== |
7 | - |
8 | -A widget used when setting the geographic location of a given person. |
9 | - |
10 | - >>> from lp.services.webapp.servers import LaunchpadTestRequest |
11 | - >>> from lp.app.widgets.location import LocationWidget |
12 | - >>> from lp.registry.interfaces.person import IPersonSet |
13 | - >>> from lp.services.fields import LocationField |
14 | - >>> salgado = getUtility(IPersonSet).getByName('salgado') |
15 | - >>> field = LocationField(__name__='location', title=u'Location') |
16 | - |
17 | - >>> bound_field = field.bind(salgado) |
18 | - >>> request = LaunchpadTestRequest( |
19 | - ... # Let's pretend requests are coming from Brazil. |
20 | - ... environ={'REMOTE_ADDR': '201.13.165.145'}) |
21 | - |
22 | -When rendering salgado's location widget to himself, we'll center the |
23 | -map around the location where he seems to be, based on the IP address of |
24 | -the request. |
25 | - |
26 | - >>> login_person(salgado) |
27 | - >>> widget = LocationWidget(bound_field, request) |
28 | - >>> widget.zoom |
29 | - 7 |
30 | - >>> widget.center_lat |
31 | - -23... |
32 | - >>> widget.center_lng |
33 | - -45... |
34 | - |
35 | -Since salgado's location hasn't been specified yet, there'll be no |
36 | -visual marker indicating where he is in the map. |
37 | - |
38 | - >>> widget.show_marker |
39 | - 0 |
40 | - |
41 | -If someone else sees salgado's location widget, though, we have no way |
42 | -of guessing what coordinates to center the map around (because the |
43 | -request doesn't come from salgado's browser), so we won't try to do so. |
44 | - |
45 | - >>> login(ANONYMOUS) |
46 | - >>> widget = LocationWidget(bound_field, request) |
47 | - >>> widget.zoom |
48 | - 2 |
49 | - >>> widget.center_lat |
50 | - 15.0 |
51 | - >>> widget.center_lng |
52 | - 0.0 |
53 | - >>> widget.show_marker |
54 | - 0 |
55 | - |
56 | -When rendering the location widget of someone who already specified a |
57 | -location, we'll obviously center the map around that, but we'll also put |
58 | -a visual marker in the latitude/longitude specified as that person's |
59 | -location. |
60 | - |
61 | - >>> kamion = getUtility(IPersonSet).getByName('kamion') |
62 | - >>> bound_field = field.bind(kamion) |
63 | - >>> login_person(kamion) |
64 | - >>> widget = LocationWidget(bound_field, request) |
65 | - >>> widget.zoom |
66 | - 9 |
67 | - >>> widget.center_lat |
68 | - 52... |
69 | - |
70 | -This next test fails in ec2 but passes locally. On ec2, the printed value |
71 | -was 0.2999999. The round() should have fixed that. Testing shows that |
72 | -round() is broken in Python 2.6 and works in 2.7 |
73 | -We'll disable this for now and fix in a cleanup branch. |
74 | - |
75 | -round(widget.center_lng, 5) |
76 | -0.3... |
77 | - |
78 | - >>> widget.show_marker |
79 | - 1 |
80 | - |
81 | -The widget's getInputValue() method will return a LocationValue object, |
82 | -which stored the geographic coordinates and the timezone. |
83 | - |
84 | - >>> request = LaunchpadTestRequest( |
85 | - ... form={'field.location.time_zone': 'Europe/London', |
86 | - ... 'field.location.latitude': '51.3', |
87 | - ... 'field.location.longitude': '0.32'}) |
88 | - >>> widget = LocationWidget(bound_field, request) |
89 | - >>> widget.hasInput() |
90 | - True |
91 | - >>> location = widget.getInputValue() |
92 | - >>> location |
93 | - <lp.app.widgets.location.LocationValue... |
94 | - >>> location.latitude # Only the integral part due to rounding errors. |
95 | - 51... |
96 | - >>> location.longitude |
97 | - 0.32... |
98 | - >>> location.time_zone |
99 | - 'Europe/London' |
100 | - |
101 | -If we try to set only the latitude *or* longitude, but not both, we'll |
102 | -get an error. |
103 | - |
104 | - >>> request = LaunchpadTestRequest( |
105 | - ... form={'field.location.time_zone': 'Europe/London', |
106 | - ... 'field.location.longitude': '0.32'}) |
107 | - >>> widget = LocationWidget(bound_field, request) |
108 | - >>> widget.hasInput() |
109 | - True |
110 | - >>> widget.getInputValue() |
111 | - Traceback (most recent call last): |
112 | - ... |
113 | - WidgetInputError:...Please provide both latitude and longitude... |
114 | - |
115 | -We also get errors if we don't specify a time zone or if the |
116 | -latitude/longitude have non-realistic values. |
117 | - |
118 | - >>> request = LaunchpadTestRequest( |
119 | - ... form={'field.location.latitude': '51.3', |
120 | - ... 'field.location.longitude': '0.32'}) |
121 | - >>> widget = LocationWidget(bound_field, request) |
122 | - >>> widget.hasInput() |
123 | - True |
124 | - >>> location = widget.getInputValue() |
125 | - Traceback (most recent call last): |
126 | - ... |
127 | - MissingInputError: ('field.location.time_zone'... |
128 | - |
129 | - >>> request = LaunchpadTestRequest( |
130 | - ... form={'field.location.time_zone': 'Europe/London', |
131 | - ... 'field.location.latitude': '99.3', |
132 | - ... 'field.location.longitude': '0.32'}) |
133 | - >>> widget = LocationWidget(bound_field, request) |
134 | - >>> widget.hasInput() |
135 | - True |
136 | - >>> widget.getInputValue() |
137 | - Traceback (most recent call last): |
138 | - ... |
139 | - WidgetInputError:...Please provide a more realistic latitude and |
140 | - longitude... |
141 | - |
142 | - |
143 | -The widget's HTML |
144 | ------------------ |
145 | - |
146 | -The widget's HTML will include <input> elements for the latitude, |
147 | -longitude and time zone fields. The values of these elements will be |
148 | -set from within Javascript whenever the user changes the location on |
149 | -the map. |
150 | - |
151 | - >>> bound_field = field.bind(kamion) |
152 | - >>> request = LaunchpadTestRequest( |
153 | - ... environ={'REMOTE_ADDR': '201.13.165.145'}) |
154 | - >>> login_person(kamion) |
155 | - >>> widget = LocationWidget(bound_field, request) |
156 | - >>> print widget() |
157 | - <input...name="field.location.latitude"...type="hidden"... |
158 | - <input...name="field.location.longitude"...type="hidden"... |
159 | - <select...name="field.location.time_zone"... |
160 | - |
161 | - |
162 | -The widget's script |
163 | -------------------- |
164 | - |
165 | -The widget initializes the mapping.renderPersonMap() JavaScript methods |
166 | -with its map_javascript property. The widget uses the |
167 | -mapping.setLocation() method to lookup the time zone for the location. |
168 | -The launchpad.geonames_identity config key provides the identity required to |
169 | -access ba-ws.geonames.net's service. |
170 | - |
171 | - >>> from lp.services.config import config |
172 | - |
173 | - >>> config.push('geoname_data', """ |
174 | - ... [launchpad] |
175 | - ... geonames_identity: totoro |
176 | - ... """) |
177 | - >>> print widget.map_javascript |
178 | - <BLANKLINE> |
179 | - <script type="text/javascript"> |
180 | - LPJS.use('node', 'lp.app.mapping', function(Y) { |
181 | - function renderMap() { |
182 | - Y.lp.app.mapping.renderPersonMap( |
183 | - 52.2, 0.3, "Colin Watson", |
184 | - 'kamion', '<img ... />', 'totoro', |
185 | - 'field.location.latitude', 'field.location.longitude', |
186 | - 'field.location.time_zone', 9, 1); |
187 | - } |
188 | - Y.on("domready", renderMap); |
189 | - }); |
190 | - </script> |
191 | - |
192 | - # Restore the config the the default state. |
193 | - >>> config_data = config.pop('geoname_data') |
194 | - |
195 | - |
196 | -XSS |
197 | ---- |
198 | - |
199 | -The widget must escape and JS encode the person's displayname to prevent |
200 | -XSS attacks and to make sure the generated javascript can be parsed. |
201 | - |
202 | - >>> kamion.displayname = ( |
203 | - ... "<script>alert('John \"nasty\" O\'Brien');</script>") |
204 | - >>> bound_field = field.bind(kamion) |
205 | - >>> widget = LocationWidget(bound_field, request) |
206 | - >>> print widget.map_javascript |
207 | - <BLANKLINE> |
208 | - ... "<script>alert('John "nasty" O'Brien');</script>",... |
209 | |
210 | === removed file 'lib/lp/app/widgets/location.py' |
211 | --- lib/lp/app/widgets/location.py 2012-02-01 15:46:43 +0000 |
212 | +++ lib/lp/app/widgets/location.py 1970-01-01 00:00:00 +0000 |
213 | @@ -1,192 +0,0 @@ |
214 | -# Copyright 2009-2010 Canonical Ltd. This software is licensed under the |
215 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
216 | - |
217 | -# pylint: disable-msg=E0702 |
218 | - |
219 | -__metaclass__ = type |
220 | -__all__ = [ |
221 | - 'ILocationWidget', |
222 | - 'LocationWidget', |
223 | - ] |
224 | - |
225 | -from lazr.restful.utils import safe_js_escape |
226 | -from z3c.ptcompat import ViewPageTemplateFile |
227 | -from zope.app.form import InputWidget |
228 | -from zope.app.form.browser.interfaces import IBrowserWidget |
229 | -from zope.app.form.browser.widget import BrowserWidget |
230 | -from zope.app.form.interfaces import ( |
231 | - IInputWidget, |
232 | - WidgetInputError, |
233 | - ) |
234 | -from zope.component import getUtility |
235 | -from zope.formlib import form |
236 | -from zope.interface import implements |
237 | -from zope.schema import ( |
238 | - Choice, |
239 | - Float, |
240 | - ) |
241 | - |
242 | -from lp import _ |
243 | -from lp.app.browser.tales import ObjectImageDisplayAPI |
244 | -from lp.app.validators import LaunchpadValidationError |
245 | -from lp.registry.interfaces.location import IObjectWithLocation |
246 | -from lp.services.config import config |
247 | -from lp.services.geoip.interfaces import IGeoIPRecord |
248 | -from lp.services.webapp.interfaces import ( |
249 | - ILaunchBag, |
250 | - IMultiLineWidgetLayout, |
251 | - ) |
252 | - |
253 | - |
254 | -class ILocationWidget(IInputWidget, IBrowserWidget, IMultiLineWidgetLayout): |
255 | - """A widget for selecting a location and time zone.""" |
256 | - |
257 | - |
258 | -class LocationValue: |
259 | - """A location passed back from a LocationWidget. |
260 | - |
261 | - This is a single object which contains the latitude, longitude and time |
262 | - zone of the location. |
263 | - """ |
264 | - |
265 | - def __init__(self, latitude, longitude, time_zone): |
266 | - self.latitude = latitude |
267 | - self.longitude = longitude |
268 | - self.time_zone = time_zone |
269 | - |
270 | - |
271 | -class LocationWidget(BrowserWidget, InputWidget): |
272 | - """See `ILocationWidget`.""" |
273 | - implements(ILocationWidget) |
274 | - |
275 | - __call__ = ViewPageTemplateFile("templates/location.pt") |
276 | - |
277 | - def __init__(self, context, request): |
278 | - super(LocationWidget, self).__init__(context, request) |
279 | - fields = form.Fields( |
280 | - Float(__name__='latitude', title=_('Latitude'), required=False), |
281 | - Float(__name__='longitude', title=_('Longitude'), required=False), |
282 | - Choice( |
283 | - __name__='time_zone', vocabulary='TimezoneName', |
284 | - title=_('Time zone'), required=True, |
285 | - description=_( |
286 | - 'Once the time zone is correctly set, events ' |
287 | - 'in Launchpad will be displayed in local time.'))) |
288 | - # This will be the initial zoom level and center of the map. |
289 | - self.zoom = 2 |
290 | - self.center_lat = 15.0 |
291 | - self.center_lng = 0.0 |
292 | - # By default, we will not show a marker initially, because we are |
293 | - # not absolutely certain of the location we are proposing. The |
294 | - # variable is a number that will be passed to JavaScript and |
295 | - # evaluated as a boolean. |
296 | - self.show_marker = 0 |
297 | - data = { |
298 | - 'time_zone': None, |
299 | - 'latitude': None, |
300 | - 'longitude': None, |
301 | - } |
302 | - # If we are creating a record for ourselves, then we will default to |
303 | - # a location GeoIP guessed, and a higher zoom. |
304 | - if getUtility(ILaunchBag).user == context.context: |
305 | - geo_request = IGeoIPRecord(request) |
306 | - self.zoom = 7 |
307 | - self.center_lat = geo_request.latitude |
308 | - self.center_lng = geo_request.longitude |
309 | - data['time_zone'] = geo_request.time_zone |
310 | - current_location = IObjectWithLocation(self.context.context) |
311 | - if current_location.latitude is not None: |
312 | - # We are updating a location. |
313 | - data['latitude'] = current_location.latitude |
314 | - data['longitude'] = current_location.longitude |
315 | - self.center_lat = current_location.latitude |
316 | - self.center_lng = current_location.longitude |
317 | - self.zoom = 9 |
318 | - self.show_marker = 1 |
319 | - if current_location.time_zone is not None: |
320 | - # We are updating a time zone. |
321 | - data['time_zone'] = current_location.time_zone |
322 | - self.initial_values = data |
323 | - widgets = form.setUpWidgets( |
324 | - fields, self.name, context, request, ignore_request=False, |
325 | - data=data) |
326 | - self.time_zone_widget = widgets['time_zone'] |
327 | - self.latitude_widget = widgets['latitude'] |
328 | - self.longitude_widget = widgets['longitude'] |
329 | - |
330 | - @property |
331 | - def map_javascript(self): |
332 | - """The Javascript code necessary to render the map.""" |
333 | - person = self.context.context |
334 | - replacements = dict( |
335 | - center_lat=self.center_lat, |
336 | - center_lng=self.center_lng, |
337 | - displayname=safe_js_escape(person.displayname), |
338 | - name=person.name, |
339 | - logo_html=ObjectImageDisplayAPI(person).logo(), |
340 | - geoname=config.launchpad.geonames_identity, |
341 | - lat_name=self.latitude_widget.name, |
342 | - lng_name=self.longitude_widget.name, |
343 | - tz_name=self.time_zone_widget.name, |
344 | - zoom=self.zoom, |
345 | - show_marker=self.show_marker) |
346 | - return """ |
347 | - <script type="text/javascript"> |
348 | - LPJS.use('node', 'lp.app.mapping', function(Y) { |
349 | - function renderMap() { |
350 | - Y.lp.app.mapping.renderPersonMap( |
351 | - %(center_lat)s, %(center_lng)s, %(displayname)s, |
352 | - '%(name)s', '%(logo_html)s', '%(geoname)s', |
353 | - '%(lat_name)s', '%(lng_name)s', '%(tz_name)s', |
354 | - %(zoom)s, %(show_marker)s); |
355 | - } |
356 | - Y.on("domready", renderMap); |
357 | - }); |
358 | - </script> |
359 | - """ % replacements |
360 | - |
361 | - def hasInput(self): |
362 | - """See `IBrowserWidget`. |
363 | - |
364 | - Return True if time zone or latitude widgets have input. |
365 | - """ |
366 | - return (self.time_zone_widget.hasInput() |
367 | - or self.latitude_widget.hasInput()) |
368 | - |
369 | - def getInputValue(self): |
370 | - """See `IBrowserWidget`. |
371 | - |
372 | - Return a `LocationValue` object containing the latitude, longitude and |
373 | - time zone chosen. |
374 | - """ |
375 | - self._error = None |
376 | - time_zone = self.time_zone_widget.getInputValue() |
377 | - latitude = None |
378 | - longitude = None |
379 | - if self.latitude_widget.hasInput(): |
380 | - latitude = self.latitude_widget.getInputValue() |
381 | - if self.longitude_widget.hasInput(): |
382 | - longitude = self.longitude_widget.getInputValue() |
383 | - if time_zone is None: |
384 | - self._error = WidgetInputError( |
385 | - self.name, self.label, |
386 | - LaunchpadValidationError( |
387 | - _('Please provide a valid time zone.'))) |
388 | - raise self._error |
389 | - if ((latitude is None and longitude is not None) |
390 | - or (latitude is not None and longitude is None)): |
391 | - # We must receive both a latitude and a longitude. |
392 | - self._error = WidgetInputError( |
393 | - self.name, self.label, |
394 | - LaunchpadValidationError( |
395 | - _('Please provide both latitude and longitude.'))) |
396 | - raise self._error |
397 | - if latitude is not None: |
398 | - if abs(latitude) > 90 or abs(longitude) > 180: |
399 | - # We need latitude and longitude within range. |
400 | - self._error = WidgetInputError( |
401 | - self.name, self.label, LaunchpadValidationError( |
402 | - _('Please provide a more realistic latitude ' |
403 | - 'and longitude.'))) |
404 | - raise self._error |
405 | - return LocationValue(latitude, longitude, time_zone) |
406 | |
407 | === modified file 'lib/lp/app/widgets/templates/location.pt' |
408 | --- lib/lp/app/widgets/templates/location.pt 2010-09-21 03:30:43 +0000 |
409 | +++ lib/lp/app/widgets/templates/location.pt 2012-02-17 12:13:23 +0000 |
410 | @@ -2,8 +2,6 @@ |
411 | xmlns:tal="http://xml.zope.org/namespaces/tal" |
412 | xmlns:i18n="http://xml.zope.org/namespaces/i18n" |
413 | omit-tag=""> |
414 | - <tal:latitude replace="structure view/latitude_widget/hidden" /> |
415 | - <tal:longitude replace="structure view/longitude_widget/hidden" /> |
416 | <div> |
417 | <tal:time-zone replace="structure view/time_zone_widget" /> |
418 | </div> |
419 | |
420 | === modified file 'lib/lp/blueprints/browser/tests/sprintattendance-views.txt' |
421 | --- lib/lp/blueprints/browser/tests/sprintattendance-views.txt 2010-07-30 12:56:27 +0000 |
422 | +++ lib/lp/blueprints/browser/tests/sprintattendance-views.txt 2012-02-17 12:13:23 +0000 |
423 | @@ -204,23 +204,3 @@ |
424 | |
425 | >>> print lines[-1] |
426 | name12,Sample Person,...Australia/Perth,...True |
427 | - |
428 | -However, some people may set their location/timezone as hidden, so if |
429 | -that's the case we won't include the person's timezone. |
430 | - |
431 | - >>> person = factory.makePerson( |
432 | - ... name='ubz-last-attendee', time_zone='Europe/London') |
433 | - >>> login_person(person) |
434 | - >>> person.setLocationVisibility(False) |
435 | - >>> dates = ['2005-10-17 09:00', '2005-11-17 19:05'] |
436 | - >>> create_sprint_attendance_view(ubz, dates).initialize() |
437 | - |
438 | - >>> view = create_view(ubz, '+attendees-csv') |
439 | - >>> last_attendee = view.render().split('\n')[-2] |
440 | - >>> print last_attendee |
441 | - ubz-last-attendee,... |
442 | - |
443 | - >>> 'Europe' in last_attendee |
444 | - False |
445 | - |
446 | - |
447 | |
448 | === modified file 'lib/lp/registry/browser/configure.zcml' |
449 | --- lib/lp/registry/browser/configure.zcml 2012-02-13 21:48:25 +0000 |
450 | +++ lib/lp/registry/browser/configure.zcml 2012-02-17 12:13:23 +0000 |
451 | @@ -841,7 +841,7 @@ |
452 | <browser:page |
453 | name="+editlocation" |
454 | for="lp.registry.interfaces.person.IPerson" |
455 | - class="lp.registry.browser.person.PersonEditLocationView" |
456 | + class="lp.registry.browser.person.PersonEditTimeZoneView" |
457 | permission="launchpad.Edit" |
458 | template="../../app/templates/generic-edit.pt"/> |
459 | <browser:page |
460 | @@ -946,9 +946,6 @@ |
461 | <browser:page |
462 | name="+xrds" |
463 | attribute="xrds"/> |
464 | - <browser:page |
465 | - name="+portlet-map" |
466 | - template="../templates/person-portlet-map.pt"/> |
467 | </browser:pages> |
468 | <browser:page |
469 | for="lp.registry.interfaces.person.IPerson" |
470 | @@ -1070,12 +1067,6 @@ |
471 | template="../templates/team-portlet-polls.pt"/> |
472 | <browser:page |
473 | for="lp.registry.interfaces.person.ITeam" |
474 | - permission="zope.Public" |
475 | - class="lp.registry.browser.team.TeamMapView" |
476 | - name="+map" |
477 | - template="../templates/team-map.pt"/> |
478 | - <browser:page |
479 | - for="lp.registry.interfaces.person.ITeam" |
480 | class="lp.registry.browser.team.TeamMailingListSubscribersView" |
481 | permission="zope.Public" |
482 | name="+mailing-list-subscribers" |
483 | @@ -1083,18 +1074,6 @@ |
484 | <browser:page |
485 | for="lp.registry.interfaces.person.ITeam" |
486 | permission="zope.Public" |
487 | - class="lp.registry.browser.team.TeamMapData" |
488 | - template="../templates/team-map-data.pt" |
489 | - name="+mapdata"/> |
490 | - <browser:page |
491 | - for="lp.registry.interfaces.person.ITeam" |
492 | - permission="zope.Public" |
493 | - class="lp.registry.browser.team.TeamMapLtdData" |
494 | - template="../templates/team-map-data.pt" |
495 | - name="+mapdataltd"/> |
496 | - <browser:page |
497 | - for="lp.registry.interfaces.person.ITeam" |
498 | - permission="zope.Public" |
499 | name="+listing-simple" |
500 | template="../templates/team-listing-simple.pt"/> |
501 | <browser:page |
502 | @@ -1105,12 +1084,6 @@ |
503 | class="lp.registry.browser.team.TeamMugshotView"/> |
504 | <browser:page |
505 | for="lp.registry.interfaces.person.ITeam" |
506 | - class="lp.registry.browser.team.TeamMapLtdView" |
507 | - permission="zope.Public" |
508 | - name="+portlet-map" |
509 | - template="../templates/team-portlet-map.pt"/> |
510 | - <browser:page |
511 | - for="lp.registry.interfaces.person.ITeam" |
512 | class="lp.registry.browser.team.TeamIndexView" |
513 | permission="zope.Public" |
514 | name="+portlet-membership" |
515 | |
516 | === modified file 'lib/lp/registry/browser/person.py' |
517 | --- lib/lp/registry/browser/person.py 2012-02-14 15:32:31 +0000 |
518 | +++ lib/lp/registry/browser/person.py 2012-02-17 12:13:23 +0000 |
519 | @@ -26,7 +26,7 @@ |
520 | 'PersonEditHomePageView', |
521 | 'PersonEditIRCNicknamesView', |
522 | 'PersonEditJabberIDsView', |
523 | - 'PersonEditLocationView', |
524 | + 'PersonEditTimeZoneView', |
525 | 'PersonEditSSHKeysView', |
526 | 'PersonEditView', |
527 | 'PersonFacets', |
528 | @@ -114,7 +114,6 @@ |
529 | from zope.interface.interface import invariant |
530 | from zope.publisher.interfaces import NotFound |
531 | from zope.schema import ( |
532 | - Bool, |
533 | Choice, |
534 | Text, |
535 | TextLine, |
536 | @@ -155,7 +154,6 @@ |
537 | LaunchpadRadioWidget, |
538 | LaunchpadRadioWidgetWithDescription, |
539 | ) |
540 | -from lp.app.widgets.location import LocationWidget |
541 | from lp.blueprints.browser.specificationtarget import HasSpecificationsView |
542 | from lp.blueprints.enums import SpecificationFilter |
543 | from lp.bugs.browser.bugtask import BugTaskSearchListingView |
544 | @@ -222,7 +220,6 @@ |
545 | from lp.services.config import config |
546 | from lp.services.database.sqlbase import flush_database_updates |
547 | from lp.services.feeds.browser import FeedsMixin |
548 | -from lp.services.fields import LocationField |
549 | from lp.services.geoip.interfaces import IRequestPreferredLanguages |
550 | from lp.services.gpg.interfaces import ( |
551 | GPGKeyNotFoundError, |
552 | @@ -3024,47 +3021,6 @@ |
553 | "mailing list.")) |
554 | self.request.response.redirect(canonical_url(self.context)) |
555 | |
556 | - @property |
557 | - def map_portlet_html(self): |
558 | - """Generate the HTML which shows the map portlet.""" |
559 | - assert self.has_visible_location, ( |
560 | - "Can't generate the map for a person who hasn't set a " |
561 | - "visible location.") |
562 | - replacements = {'center_lat': self.context.latitude, |
563 | - 'center_lng': self.context.longitude} |
564 | - return u""" |
565 | - <script type="text/javascript"> |
566 | - LPJS.use('node', 'lp.app.mapping', function(Y) { |
567 | - function renderMap() { |
568 | - Y.lp.app.mapping.renderPersonMapSmall( |
569 | - %(center_lat)s, %(center_lng)s); |
570 | - } |
571 | - Y.on("domready", renderMap); |
572 | - }); |
573 | - </script>""" % replacements |
574 | - |
575 | - @cachedproperty |
576 | - def has_visible_location(self): |
577 | - """Does the person have latitude and a visible location.""" |
578 | - if self.context.is_team: |
579 | - return self.context.mapped_participants_count > 0 |
580 | - else: |
581 | - return (check_permission('launchpad.View', self.context.location) |
582 | - and self.context.latitude is not None) |
583 | - |
584 | - @property |
585 | - def should_show_map_portlet(self): |
586 | - """Should the map portlet be displayed? |
587 | - |
588 | - The map portlet is displayed only if the person has no location |
589 | - specified (latitude), or if the user has permission to view the |
590 | - person's location. |
591 | - """ |
592 | - if self.user == self.context: |
593 | - return True |
594 | - else: |
595 | - return self.has_visible_location |
596 | - |
597 | |
598 | class PersonCodeOfConductEditView(LaunchpadView): |
599 | """View for the ~person/+codesofconduct pages.""" |
600 | @@ -4762,22 +4718,19 @@ |
601 | canonical_url(self.context, view_name='+oauth-tokens')) |
602 | |
603 | |
604 | -class PersonLocationForm(Interface): |
605 | - |
606 | - location = LocationField( |
607 | - title=_('Time zone'), |
608 | - required=True) |
609 | - hide = Bool( |
610 | - title=_("Hide my location details from others."), |
611 | - required=True, default=False) |
612 | - |
613 | - |
614 | -class PersonEditLocationView(LaunchpadFormView): |
615 | - """Edit a person's location.""" |
616 | - |
617 | - schema = PersonLocationForm |
618 | - field_names = ['location'] |
619 | - custom_widget('location', LocationWidget) |
620 | +class PersonTimeZoneForm(Interface): |
621 | + |
622 | + time_zone = Choice( |
623 | + vocabulary='TimezoneName', title=_('Time zone'), required=True, |
624 | + description=_( |
625 | + 'Once the time zone is correctly set, events ' |
626 | + 'in Launchpad will be displayed in local time.')) |
627 | + |
628 | + |
629 | +class PersonEditTimeZoneView(LaunchpadFormView): |
630 | + """Edit a person's time zone.""" |
631 | + |
632 | + schema = PersonTimeZoneForm |
633 | page_title = label = 'Set timezone' |
634 | |
635 | @property |
636 | @@ -4788,17 +4741,14 @@ |
637 | |
638 | @action(_("Update"), name="update") |
639 | def action_update(self, action, data): |
640 | - """Set the coordinates and time zone for the person.""" |
641 | - new_location = data.get('location') |
642 | - if new_location is None: |
643 | + """Set the time zone for the person.""" |
644 | + timezone = data.get('time_zone') |
645 | + if timezone is None: |
646 | raise UnexpectedFormData('No location received.') |
647 | - latitude = new_location.latitude |
648 | - longitude = new_location.longitude |
649 | - time_zone = new_location.time_zone |
650 | - self.context.setLocation(latitude, longitude, time_zone, self.user) |
651 | - if 'hide' in self.field_names: |
652 | - visible = not data['hide'] |
653 | - self.context.setLocationVisibility(visible) |
654 | + # XXX salgado, 2012-02-16, bug=933699: Use setLocation() because it's |
655 | + # the cheaper way to set the timezone of a person. Once the bug is |
656 | + # fixed we'll be able to get rid of this hack. |
657 | + self.context.setLocation(None, None, timezone, self.user) |
658 | |
659 | |
660 | def archive_to_person(archive): |
661 | |
662 | === modified file 'lib/lp/registry/browser/team.py' |
663 | --- lib/lp/registry/browser/team.py 2012-02-16 03:12:17 +0000 |
664 | +++ lib/lp/registry/browser/team.py 2012-02-17 12:13:23 +0000 |
665 | @@ -20,10 +20,6 @@ |
666 | 'TeamMailingListModerationView', |
667 | 'TeamMailingListSubscribersView', |
668 | 'TeamMailingListArchiveView', |
669 | - 'TeamMapData', |
670 | - 'TeamMapLtdData', |
671 | - 'TeamMapView', |
672 | - 'TeamMapLtdView', |
673 | 'TeamMemberAddView', |
674 | 'TeamMembershipView', |
675 | 'TeamMugshotView', |
676 | @@ -1206,112 +1202,6 @@ |
677 | self.widgets['newmember'].setRenderedValue(None) |
678 | |
679 | |
680 | -class TeamMapView(LaunchpadView): |
681 | - """Show all people with known locations on a map. |
682 | - |
683 | - Also provides links to edit the locations of people in the team without |
684 | - known locations. |
685 | - """ |
686 | - |
687 | - label = "Team member locations" |
688 | - limit = None |
689 | - |
690 | - @cachedproperty |
691 | - def mapped_participants(self): |
692 | - """Participants with locations.""" |
693 | - return self.context.getMappedParticipants(limit=self.limit) |
694 | - |
695 | - @cachedproperty |
696 | - def mapped_participants_count(self): |
697 | - """Count of participants with locations.""" |
698 | - return self.context.mapped_participants_count |
699 | - |
700 | - @cachedproperty |
701 | - def has_mapped_participants(self): |
702 | - """Does the team have any mapped participants?""" |
703 | - return self.mapped_participants_count > 0 |
704 | - |
705 | - @cachedproperty |
706 | - def unmapped_participants(self): |
707 | - """Participants (ordered by name) with no recorded locations.""" |
708 | - return list(self.context.unmapped_participants) |
709 | - |
710 | - @cachedproperty |
711 | - def unmapped_participants_count(self): |
712 | - """Count of participants with no recorded locations.""" |
713 | - return self.context.unmapped_participants_count |
714 | - |
715 | - @cachedproperty |
716 | - def times(self): |
717 | - """The current times in time zones with members.""" |
718 | - zones = set(participant.time_zone |
719 | - for participant in self.mapped_participants) |
720 | - times = [datetime.now(pytz.timezone(zone)) |
721 | - for zone in zones] |
722 | - timeformat = '%H:%M' |
723 | - return sorted( |
724 | - set(time.strftime(timeformat) for time in times)) |
725 | - |
726 | - @cachedproperty |
727 | - def bounds(self): |
728 | - """A dictionary with the bounds and center of the map, or None""" |
729 | - if self.has_mapped_participants: |
730 | - return self.context.getMappedParticipantsBounds(self.limit) |
731 | - return None |
732 | - |
733 | - @property |
734 | - def map_html(self): |
735 | - """HTML which shows the map with location of the team's members.""" |
736 | - return """ |
737 | - <script type="text/javascript"> |
738 | - LPJS.use('node', 'lp.app.mapping', function(Y) { |
739 | - function renderMap() { |
740 | - Y.lp.app.mapping.renderTeamMap( |
741 | - %(min_lat)s, %(max_lat)s, %(min_lng)s, |
742 | - %(max_lng)s, %(center_lat)s, %(center_lng)s); |
743 | - } |
744 | - Y.on("domready", renderMap); |
745 | - }); |
746 | - </script>""" % self.bounds |
747 | - |
748 | - @property |
749 | - def map_portlet_html(self): |
750 | - """The HTML which shows a small version of the team's map.""" |
751 | - return """ |
752 | - <script type="text/javascript"> |
753 | - LPJS.use('node', 'lp.app.mapping', function(Y) { |
754 | - function renderMap() { |
755 | - Y.lp.app.mapping.renderTeamMapSmall( |
756 | - %(center_lat)s, %(center_lng)s); |
757 | - } |
758 | - Y.on("domready", renderMap); |
759 | - }); |
760 | - </script>""" % self.bounds |
761 | - |
762 | - |
763 | -class TeamMapData(TeamMapView): |
764 | - """An XML dump of the locations of all team members.""" |
765 | - |
766 | - def render(self): |
767 | - self.request.response.setHeader( |
768 | - 'content-type', 'application/xml;charset=utf-8') |
769 | - body = LaunchpadView.render(self) |
770 | - return body.encode('utf-8') |
771 | - |
772 | - |
773 | -class TeamMapLtdMixin: |
774 | - """A mixin for team views with limited participants.""" |
775 | - limit = 24 |
776 | - |
777 | - |
778 | -class TeamMapLtdView(TeamMapLtdMixin, TeamMapView): |
779 | - """Team map view with limited participants.""" |
780 | - |
781 | - |
782 | -class TeamMapLtdData(TeamMapLtdMixin, TeamMapData): |
783 | - """An XML dump of the locations of limited number of team members.""" |
784 | - |
785 | - |
786 | class TeamNavigation(PersonNavigation): |
787 | |
788 | usedfor = ITeam |
789 | @@ -1603,11 +1493,6 @@ |
790 | text = 'Approve or decline members' |
791 | return Link(target, text, icon='add') |
792 | |
793 | - def map(self): |
794 | - target = '+map' |
795 | - text = 'View map and time zones' |
796 | - return Link(target, text, icon='meeting') |
797 | - |
798 | def add_my_teams(self): |
799 | target = '+add-my-teams' |
800 | text = 'Add one of my teams' |
801 | @@ -1723,7 +1608,6 @@ |
802 | 'configure_mailing_list', |
803 | 'moderate_mailing_list', |
804 | 'editlanguages', |
805 | - 'map', |
806 | 'polls', |
807 | 'add_poll', |
808 | 'join', |
809 | |
810 | === modified file 'lib/lp/registry/browser/tests/team-views.txt' |
811 | --- lib/lp/registry/browser/tests/team-views.txt 2012-02-10 19:38:27 +0000 |
812 | +++ lib/lp/registry/browser/tests/team-views.txt 2012-02-17 12:13:23 +0000 |
813 | @@ -67,135 +67,6 @@ |
814 | form fields. |
815 | |
816 | |
817 | -+map-portlet |
818 | ------------- |
819 | - |
820 | -The team profile page contain the location portlet that shows a map. |
821 | - |
822 | - >>> team_view = create_initialized_view(ubuntu_team, '+index') |
823 | - >>> team_view.has_visible_location |
824 | - False |
825 | - |
826 | -After a member has set his location, the map will be rendered. |
827 | - |
828 | - >>> login_person(sample_person) |
829 | - >>> sample_person.setLocation( |
830 | - ... 38.81, 77.1, 'America/New_York', sample_person) |
831 | - |
832 | - >>> team_view = create_initialized_view(ubuntu_team, '+index') |
833 | - >>> team_view.has_visible_location |
834 | - True |
835 | - |
836 | -The small_maps key in the launchpad_views cookie can be set by the viewing |
837 | -user to 'false' to indicate that small maps are not wanted. |
838 | - |
839 | - >>> cookie = 'launchpad_views=small_maps=false' |
840 | - >>> team_view = create_initialized_view( |
841 | - ... ubuntu_team, '+index', cookie=cookie) |
842 | - |
843 | -+map |
844 | ----- |
845 | - |
846 | -A team's +map page will show a map with markers on the location of all |
847 | -members of that team. That page also lists the local times of members. |
848 | - |
849 | - >>> login('foo.bar@canonical.com') |
850 | - >>> context = factory.makeTeam(salgado) |
851 | - >>> context.mapped_participants_count |
852 | - 0 |
853 | - >>> view = create_initialized_view(context, '+map') |
854 | - >>> view.times |
855 | - [] |
856 | - |
857 | - |
858 | -Once a member is mapped, the map will be rendered. The view provides a cached |
859 | -property to access the mapped participants. The views number of times is |
860 | -incremented for each timezone the members reside in. |
861 | - |
862 | - >>> london_member = factory.makePerson( |
863 | - ... latitude=51.49, longitude=-0.13, time_zone='Europe/London') |
864 | - >>> ignored = context.addMember(london_member, mark) |
865 | - >>> context.mapped_participants_count |
866 | - 1 |
867 | - >>> view = create_initialized_view(context, '+map') |
868 | - >>> len(view.mapped_participants) |
869 | - 1 |
870 | - >>> len(view.times) |
871 | - 1 |
872 | - |
873 | - >>> brazil_member = factory.makePerson( |
874 | - ... latitude=-23.60, longitude=-46.64, time_zone='America/Sao_Paulo') |
875 | - >>> ignored = context.addMember(brazil_member, mark) |
876 | - >>> context.mapped_participants_count |
877 | - 2 |
878 | - >>> view = create_initialized_view(context, '+map') |
879 | - >>> len(view.mapped_participants) |
880 | - 2 |
881 | - >>> len(view.times) |
882 | - 2 |
883 | - |
884 | -If we had two members in London, though, we wouldn't list London's time |
885 | -twice, for obvious reasons. |
886 | - |
887 | - >>> london_member2 = factory.makePerson( |
888 | - ... latitude=51.49, longitude=-0.13, time_zone='Europe/London') |
889 | - >>> ignored = context.addMember(london_member2, mark) |
890 | - >>> context.mapped_participants_count |
891 | - 3 |
892 | - >>> view = create_initialized_view(context, '+map') |
893 | - >>> len(view.times) |
894 | - 2 |
895 | - |
896 | -The number of persons returned by mapped_participants is governed by the |
897 | -view's limit attribute. The default value is None, meaning there is no |
898 | -limit. This view is used for displaying the full map. |
899 | - |
900 | - >>> print view.limit |
901 | - None |
902 | - >>> len(view.mapped_participants) |
903 | - 3 |
904 | - |
905 | -A portlet for the map also exists which is used for displaying the map |
906 | -inside the person or team page. It has the number of participants limited. |
907 | - |
908 | - >>> view = create_initialized_view(context, '+portlet-map') |
909 | - >>> view.limit |
910 | - 24 |
911 | - |
912 | - # Hack the limit to demonstrate that it controls mapped_participants. |
913 | - >>> view.limit = 1 |
914 | - >>> len(view.mapped_participants) |
915 | - 1 |
916 | - |
917 | -The view provides the count of mapped and unmapped members. The counts |
918 | -are cached to improve performance. |
919 | - |
920 | - >>> view = create_initialized_view(context, '+map') |
921 | - >>> view.mapped_participants_count |
922 | - 3 |
923 | - >>> view.unmapped_participants_count |
924 | - 1 |
925 | - |
926 | -The template can ask if the team has mapped members, so that it does not |
927 | -need to work with counts or the list of members. |
928 | - |
929 | - >>> view.has_mapped_participants |
930 | - True |
931 | - |
932 | -The cached bounds of the mapped members of the team can be used to define |
933 | -the scale of a map. |
934 | - |
935 | - >>> bounds = view.bounds |
936 | - >>> for key in sorted(bounds): |
937 | - ... print "%s: %s" % (key, bounds[key]) |
938 | - center_lat: 13... |
939 | - center_lng: -23... |
940 | - max_lat: 51... |
941 | - max_lng: -0... |
942 | - min_lat: -23... |
943 | - min_lng: -46... |
944 | - |
945 | - |
946 | Contacting the team |
947 | ------------------- |
948 | |
949 | |
950 | === modified file 'lib/lp/registry/doc/personlocation.txt' |
951 | --- lib/lp/registry/doc/personlocation.txt 2011-12-24 17:49:30 +0000 |
952 | +++ lib/lp/registry/doc/personlocation.txt 2012-02-17 12:13:23 +0000 |
953 | @@ -1,219 +1,41 @@ |
954 | Locations for People and Teams |
955 | ============================== |
956 | |
957 | -The PersonLocation object stores information about the location and time |
958 | -zone of a person. It also remembers who provided that information, and |
959 | -when. This is designed to make it possible to have people provide |
960 | -location / time zone info for other people in a wiki style. |
961 | +We no longer allow people to set their geographical locations, but their time |
962 | +zone (which they can set) is stored together with their latitude/longitude (in |
963 | +PersonLocation). Until we move the time zone back to the Person table |
964 | +(bug=933699), we'll maintain the setLocation() API on IPerson. |
965 | |
966 | >>> from lp.services.webapp.testing import verifyObject |
967 | - >>> from lp.registry.interfaces.location import IObjectWithLocation |
968 | >>> from lp.registry.interfaces.person import IPersonSet |
969 | >>> personset = getUtility(IPersonSet) |
970 | |
971 | -A Person implements the IObjectWithLocation interface. |
972 | - |
973 | - >>> login('test@canonical.com') |
974 | >>> marilize = personset.getByName('marilize') |
975 | - >>> verifyObject(IObjectWithLocation, marilize) |
976 | - True |
977 | - |
978 | -A Person has a PersonLocation record, if there is any location |
979 | -information associated with them. That implements the IPersonLocation |
980 | -interface. |
981 | - |
982 | - >>> from lp.registry.interfaces.location import IPersonLocation |
983 | - >>> marilize.location |
984 | - <PersonLocation... |
985 | - >>> verifyObject(IPersonLocation, marilize.location) |
986 | - True |
987 | - |
988 | -In some cases, a person has a time zone, but no location. |
989 | - |
990 | >>> print marilize.time_zone |
991 | Africa/Maseru |
992 | >>> print marilize.latitude |
993 | None |
994 | - |
995 | -The location for a person is set with the "setLocation" method. This |
996 | -requires that the user providing the information is passed as a |
997 | -parameter. |
998 | - |
999 | -A user cannot set another user's location. |
1000 | - |
1001 | - >>> jdub = personset.getByName('jdub') |
1002 | - >>> login_person(jdub) |
1003 | + >>> print marilize.longitude |
1004 | + None |
1005 | + |
1006 | +setLocation() will always set the time zone to the given value and both |
1007 | +latitude and longitude to None, regardless of what was passed in. |
1008 | + |
1009 | >>> cprov = personset.getByName('cprov') |
1010 | - >>> cprov.setLocation(-43.0, -62.1, 'America/Sao_Paulo', jdub) |
1011 | - Traceback (most recent call last): |
1012 | - ... |
1013 | - Unauthorized:... |
1014 | - |
1015 | -A user can set his own location. |
1016 | - |
1017 | >>> login_person(cprov) |
1018 | >>> cprov.setLocation(-43.2, -61.93, 'America/Sao_Paulo', cprov) |
1019 | - |
1020 | -cprov can change his location information. We need to deal |
1021 | -with some floating point precision issues here, hence the rounding. |
1022 | - |
1023 | - >>> login_person(cprov) |
1024 | - >>> cprov.setLocation(-43.52, -61.93, 'America/Sao_Paulo', cprov) |
1025 | - >>> abs(cprov.latitude + 43.52) < 0.001 |
1026 | - True |
1027 | - |
1028 | -Admins can set a user's location. |
1029 | - |
1030 | - >>> admin = personset.getByName('name16') |
1031 | - >>> login_person(admin) |
1032 | - >>> cprov.setLocation(-43.0, -62.1, 'America/Sao_Paulo', admin) |
1033 | - >>> abs(cprov.longitude + 62.1) < 0.001 |
1034 | - True |
1035 | + >>> print cprov.time_zone |
1036 | + America/Sao_Paulo |
1037 | + >>> print cprov.latitude |
1038 | + None |
1039 | + >>> print cprov.longitude |
1040 | + None |
1041 | |
1042 | We cannot store a location for a team, though. |
1043 | |
1044 | + >>> jdub = personset.getByName('jdub') |
1045 | >>> guadamen = personset.getByName('guadamen') |
1046 | >>> guadamen.setLocation(34.5, 23.1, 'Africa/Maseru', jdub) |
1047 | Traceback (most recent call last): |
1048 | ... |
1049 | AssertionError:... |
1050 | - |
1051 | -Nor can we set only the latitude of a person. |
1052 | - |
1053 | - >>> cprov.setLocation(-43.0, None, 'America/Sao_Paulo', admin) |
1054 | - Traceback (most recent call last): |
1055 | - ... |
1056 | - AssertionError:... |
1057 | - |
1058 | -Similarly, we can't set only the longitude. |
1059 | - |
1060 | - >>> cprov.setLocation(None, -43.0, 'America/Sao_Paulo', admin) |
1061 | - Traceback (most recent call last): |
1062 | - ... |
1063 | - AssertionError:... |
1064 | - |
1065 | -We can get lists of the participants in a team that do, or do not, have |
1066 | -locations. Specifically, we mean latitude/longitude data, not time zone |
1067 | -data. |
1068 | - |
1069 | -When we get mapped participants, and unmapped participants, we only mean |
1070 | -the individuals, not other teams. We'll show that guadamen has a |
1071 | -sub-team, ubuntu-team, and that it still does not appear in either |
1072 | -mapped_participants or unmapped_participants (although its members do). |
1073 | - |
1074 | - >>> for member in guadamen.activemembers: |
1075 | - ... if member.teamowner is not None: |
1076 | - ... print member.name |
1077 | - ubuntu-team |
1078 | - >>> len(guadamen.getMappedParticipants()) |
1079 | - 2 |
1080 | - >>> for mapped in guadamen.getMappedParticipants(): |
1081 | - ... if mapped.teamowner is not None: |
1082 | - ... print mapped.name |
1083 | - >>> guadamen.unmapped_participants.count() |
1084 | - 7 |
1085 | - >>> for unmapped in guadamen.unmapped_participants: |
1086 | - ... if unmapped.teamowner is not None: |
1087 | - ... print unmapped.name |
1088 | - |
1089 | -When we iterate over the mapped_participants in a team, their locations |
1090 | -have been pre-cached so that we don't hit the database everytime we |
1091 | -access a person's .location property. |
1092 | - |
1093 | - >>> from lp.services.propertycache import get_property_cache |
1094 | - >>> for mapped in guadamen.getMappedParticipants(): |
1095 | - ... cache = get_property_cache(mapped) |
1096 | - ... if ("location" not in cache or |
1097 | - ... not verifyObject(IPersonLocation, cache.location)): |
1098 | - ... print 'No cached location on %s' % mapped.name |
1099 | - |
1100 | -The mapped_participants methods takes a optional argument to limit the |
1101 | -number of persons in the returned list. |
1102 | - |
1103 | - >>> mapped = guadamen.getMappedParticipants(limit=1) |
1104 | - >>> len(mapped) |
1105 | - 1 |
1106 | - |
1107 | -The count of mapped and unmapped members can also be retrieved, which is |
1108 | -faster than getting the resultset of members. |
1109 | - |
1110 | - >>> guadamen.mapped_participants_count |
1111 | - 2 |
1112 | - >>> guadamen.unmapped_participants_count |
1113 | - 7 |
1114 | - |
1115 | -The bounds of the mapped members can be retrieved. It is a dict that contains |
1116 | -the minimum maximum, and central longitudes and latitudes. |
1117 | - |
1118 | - >>> bounds = guadamen.getMappedParticipantsBounds() |
1119 | - >>> for key in sorted(bounds): |
1120 | - ... print "%s: %s" % (key, bounds[key]) |
1121 | - center_lat: 4... |
1122 | - center_lng: -30... |
1123 | - max_lat: 52... |
1124 | - max_lng: 0... |
1125 | - min_lat: -43... |
1126 | - min_lng: -62... |
1127 | - |
1128 | -Calling getMappedParticipantsBounds() on a team without members is an error. |
1129 | - |
1130 | - >>> unmapped_team = factory.makeTeam() |
1131 | - >>> unmapped_team.getMappedParticipantsBounds() |
1132 | - Traceback (most recent call last): |
1133 | - ... |
1134 | - AssertionError: This method cannot be called when |
1135 | - mapped_participants_count == 0. |
1136 | - |
1137 | - |
1138 | -Location visibility |
1139 | -------------------- |
1140 | - |
1141 | -Some people may not want their location to be disclosed to others, so |
1142 | -we provide a way for them to hide their location from other users. By |
1143 | -default a person's location is visible. |
1144 | - |
1145 | - >>> salgado = personset.getByName('salgado') |
1146 | - >>> login_person(salgado) |
1147 | - >>> salgado.setLocation(-43.0, -62.1, 'America/Sao_Paulo', salgado) |
1148 | - >>> salgado.location.visible |
1149 | - True |
1150 | - >>> salgado.location.latitude |
1151 | - -43... |
1152 | - |
1153 | - >>> login_person(jdub) |
1154 | - >>> salgado.location.latitude |
1155 | - -43... |
1156 | - |
1157 | -But it can be changed through the setLocationVisibility() method. If the |
1158 | -visibility is set to False, only the person himself will be able to see |
1159 | -the location data except for time zone. |
1160 | - |
1161 | - >>> login_person(salgado) |
1162 | - >>> salgado.setLocationVisibility(False) |
1163 | - >>> salgado.location.visible |
1164 | - False |
1165 | - |
1166 | - >>> login_person(jdub) |
1167 | - >>> print salgado.time_zone |
1168 | - America/Sao_Paulo |
1169 | - >>> salgado.latitude |
1170 | - Traceback (most recent call last): |
1171 | - ... |
1172 | - Unauthorized:... |
1173 | - >>> salgado.longitude |
1174 | - Traceback (most recent call last): |
1175 | - ... |
1176 | - Unauthorized:... |
1177 | - >>> salgado.location.latitude |
1178 | - Traceback (most recent call last): |
1179 | - ... |
1180 | - Unauthorized:... |
1181 | - |
1182 | -A team's .mapped_participants will also exclude the members who made |
1183 | -their location invisible. |
1184 | - |
1185 | - >>> admins = personset.getByName('admins') |
1186 | - >>> salgado in admins.activemembers |
1187 | - True |
1188 | - >>> salgado in admins.getMappedParticipants() |
1189 | - False |
1190 | |
1191 | === modified file 'lib/lp/registry/interfaces/location.py' |
1192 | --- lib/lp/registry/interfaces/location.py 2011-12-24 16:54:44 +0000 |
1193 | +++ lib/lp/registry/interfaces/location.py 2012-02-17 12:13:23 +0000 |
1194 | @@ -47,12 +47,18 @@ |
1195 | class IHasLocation(Interface): |
1196 | """An interface supported by objects with a defined location.""" |
1197 | |
1198 | - latitude = exported(doNotSnapshot( |
1199 | - Float(title=_("The latitude of this object."), |
1200 | - required=False, readonly=True))) |
1201 | - longitude = exported(doNotSnapshot( |
1202 | - Float(title=_("The longitude of this object."), |
1203 | - required=False, readonly=True))) |
1204 | + latitude = exported( |
1205 | + doNotSnapshot( |
1206 | + Float(title=_("The latitude of this object."), |
1207 | + required=False, readonly=True)), |
1208 | + ('devel', dict(exported=False)), |
1209 | + exported=True) |
1210 | + longitude = exported( |
1211 | + doNotSnapshot( |
1212 | + Float(title=_("The longitude of this object."), |
1213 | + required=False, readonly=True)), |
1214 | + ('devel', dict(exported=False)), |
1215 | + exported=True) |
1216 | time_zone = exported(doNotSnapshot( |
1217 | Choice(title=_('The time zone of this object.'), |
1218 | required=False, readonly=True, |
1219 | |
1220 | === modified file 'lib/lp/registry/interfaces/person.py' |
1221 | --- lib/lp/registry/interfaces/person.py 2012-02-16 08:27:37 +0000 |
1222 | +++ lib/lp/registry/interfaces/person.py 2012-02-17 12:13:23 +0000 |
1223 | @@ -130,7 +130,6 @@ |
1224 | from lp.registry.interfaces.jabber import IJabberID |
1225 | from lp.registry.interfaces.location import ( |
1226 | IHasLocation, |
1227 | - ILocationRecord, |
1228 | IObjectWithLocation, |
1229 | ISetLocation, |
1230 | ) |
1231 | @@ -719,7 +718,7 @@ |
1232 | @operation_for_version("beta") |
1233 | def transitionVisibility(visibility, user): |
1234 | """Set visibility of IPerson. |
1235 | - |
1236 | + |
1237 | :param visibility: The PersonVisibility to change to. |
1238 | :param user: The user requesting the change. |
1239 | :raises: `ImmutableVisibilityError` when the visibility can not |
1240 | @@ -1611,31 +1610,6 @@ |
1241 | exported_as='proposed_members') |
1242 | proposed_member_count = Attribute("Number of PROPOSED members") |
1243 | |
1244 | - mapped_participants_count = Attribute( |
1245 | - "The number of mapped participants") |
1246 | - unmapped_participants = doNotSnapshot( |
1247 | - CollectionField( |
1248 | - title=_("List of participants with no coordinates recorded."), |
1249 | - value_type=Reference(schema=Interface))) |
1250 | - unmapped_participants_count = Attribute( |
1251 | - "The number of unmapped participants") |
1252 | - |
1253 | - def getMappedParticipants(limit=None): |
1254 | - """List of participants with coordinates. |
1255 | - |
1256 | - :param limit: The optional maximum number of items to return. |
1257 | - :return: A list of `IPerson` objects |
1258 | - """ |
1259 | - |
1260 | - def getMappedParticipantsBounds(): |
1261 | - """Return a dict of the bounding longitudes latitudes, and centers. |
1262 | - |
1263 | - This method cannot be called if there are no mapped participants. |
1264 | - |
1265 | - :return: a dict containing: min_lat, min_lng, max_lat, max_lng, |
1266 | - center_lat, and center_lng |
1267 | - """ |
1268 | - |
1269 | def getMembersWithPreferredEmails(): |
1270 | """Returns a result set of persons with precached addresses. |
1271 | |
1272 | @@ -1711,13 +1685,6 @@ |
1273 | :param team: The team to leave. |
1274 | """ |
1275 | |
1276 | - @operation_parameters( |
1277 | - visible=copy_field(ILocationRecord['visible'], required=True)) |
1278 | - @export_write_operation() |
1279 | - @operation_for_version("beta") |
1280 | - def setLocationVisibility(visible): |
1281 | - """Specify the visibility of a person's location and time zone.""" |
1282 | - |
1283 | def setMembershipData(person, status, reviewer, expires=None, |
1284 | comment=None): |
1285 | """Set the attributes of the person's membership on this team. |
1286 | @@ -2585,7 +2552,6 @@ |
1287 | 'invited_members', |
1288 | 'deactivatedmembers', |
1289 | 'expiredmembers', |
1290 | - 'unmapped_participants', |
1291 | ]: |
1292 | IPersonViewRestricted[name].value_type.schema = IPerson |
1293 | |
1294 | |
1295 | === modified file 'lib/lp/registry/model/person.py' |
1296 | --- lib/lp/registry/model/person.py 2012-02-16 08:27:37 +0000 |
1297 | +++ lib/lp/registry/model/person.py 2012-02-17 12:13:23 +0000 |
1298 | @@ -705,6 +705,24 @@ |
1299 | Or(OAuthRequestToken.date_expires == None, |
1300 | OAuthRequestToken.date_expires > UTC_NOW)) |
1301 | |
1302 | + @property |
1303 | + def latitude(self): |
1304 | + """See `IHasLocation`. |
1305 | + |
1306 | + We no longer allow users to set their geographical location but we |
1307 | + need to keep this because it was exported on version 1.0 of the API. |
1308 | + """ |
1309 | + return None |
1310 | + |
1311 | + @property |
1312 | + def longitude(self): |
1313 | + """See `IHasLocation`. |
1314 | + |
1315 | + We no longer allow users to set their geographical location but we |
1316 | + need to keep this because it was exported on version 1.0 of the API. |
1317 | + """ |
1318 | + return None |
1319 | + |
1320 | @cachedproperty |
1321 | def location(self): |
1322 | """See `IObjectWithLocation`.""" |
1323 | @@ -719,33 +737,6 @@ |
1324 | # enough rights to see it. |
1325 | return ProxyFactory(self.location).time_zone |
1326 | |
1327 | - @property |
1328 | - def latitude(self): |
1329 | - """See `IHasLocation`.""" |
1330 | - if self.location is None: |
1331 | - return None |
1332 | - # Wrap the location with a security proxy to make sure the user has |
1333 | - # enough rights to see it. |
1334 | - return ProxyFactory(self.location).latitude |
1335 | - |
1336 | - @property |
1337 | - def longitude(self): |
1338 | - """See `IHasLocation`.""" |
1339 | - if self.location is None: |
1340 | - return None |
1341 | - # Wrap the location with a security proxy to make sure the user has |
1342 | - # enough rights to see it. |
1343 | - return ProxyFactory(self.location).longitude |
1344 | - |
1345 | - def setLocationVisibility(self, visible): |
1346 | - """See `ISetLocation`.""" |
1347 | - assert not self.is_team, 'Cannot edit team location.' |
1348 | - if self.location is None: |
1349 | - get_property_cache(self).location = PersonLocation( |
1350 | - person=self, visible=visible) |
1351 | - else: |
1352 | - self.location.visible = visible |
1353 | - |
1354 | def setLocation(self, latitude, longitude, time_zone, user): |
1355 | """See `ISetLocation`.""" |
1356 | assert not self.is_team, 'Cannot edit team location.' |
1357 | @@ -2062,92 +2053,6 @@ |
1358 | ) |
1359 | return (self.subscriptionpolicy in open_types) |
1360 | |
1361 | - def _getMappedParticipantsLocations(self, limit=None): |
1362 | - """See `IPersonViewRestricted`.""" |
1363 | - return PersonLocation.select(""" |
1364 | - PersonLocation.person = TeamParticipation.person AND |
1365 | - TeamParticipation.team = %s AND |
1366 | - -- We only need to check for a latitude here because there's a DB |
1367 | - -- constraint which ensures they are both set or unset. |
1368 | - PersonLocation.latitude IS NOT NULL AND |
1369 | - PersonLocation.visible IS TRUE AND |
1370 | - Person.id = PersonLocation.person AND |
1371 | - Person.teamowner IS NULL |
1372 | - """ % sqlvalues(self.id), |
1373 | - clauseTables=['TeamParticipation', 'Person'], |
1374 | - prejoins=['person', ], limit=limit) |
1375 | - |
1376 | - def getMappedParticipants(self, limit=None): |
1377 | - """See `IPersonViewRestricted`.""" |
1378 | - # Pre-cache this location against its person. Since we'll always |
1379 | - # iterate over all persons returned by this property (to build the map |
1380 | - # of team members), it becomes more important to cache their locations |
1381 | - # than to return a lazy SelectResults (or similar) object that only |
1382 | - # fetches the rows when they're needed. |
1383 | - locations = self._getMappedParticipantsLocations(limit=limit) |
1384 | - for location in locations: |
1385 | - get_property_cache(location.person).location = location |
1386 | - participants = set(location.person for location in locations) |
1387 | - # Cache the ValidPersonCache query for all mapped participants. |
1388 | - if len(participants) > 0: |
1389 | - sql = "id IN (%s)" % ",".join(sqlvalues(*participants)) |
1390 | - list(ValidPersonCache.select(sql)) |
1391 | - getUtility(IPersonSet).cacheBrandingForPeople(participants) |
1392 | - return list(participants) |
1393 | - |
1394 | - @property |
1395 | - def mapped_participants_count(self): |
1396 | - """See `IPersonViewRestricted`.""" |
1397 | - return self._getMappedParticipantsLocations().count() |
1398 | - |
1399 | - def getMappedParticipantsBounds(self, limit=None): |
1400 | - """See `IPersonViewRestricted`.""" |
1401 | - max_lat = -90.0 |
1402 | - min_lat = 90.0 |
1403 | - max_lng = -180.0 |
1404 | - min_lng = 180.0 |
1405 | - locations = self._getMappedParticipantsLocations(limit) |
1406 | - if self.mapped_participants_count == 0: |
1407 | - raise AssertionError( |
1408 | - 'This method cannot be called when ' |
1409 | - 'mapped_participants_count == 0.') |
1410 | - latitudes = sorted(location.latitude for location in locations) |
1411 | - if latitudes[-1] > max_lat: |
1412 | - max_lat = latitudes[-1] |
1413 | - if latitudes[0] < min_lat: |
1414 | - min_lat = latitudes[0] |
1415 | - longitudes = sorted(location.longitude for location in locations) |
1416 | - if longitudes[-1] > max_lng: |
1417 | - max_lng = longitudes[-1] |
1418 | - if longitudes[0] < min_lng: |
1419 | - min_lng = longitudes[0] |
1420 | - center_lat = (max_lat + min_lat) / 2.0 |
1421 | - center_lng = (max_lng + min_lng) / 2.0 |
1422 | - return dict( |
1423 | - min_lat=min_lat, min_lng=min_lng, max_lat=max_lat, |
1424 | - max_lng=max_lng, center_lat=center_lat, center_lng=center_lng) |
1425 | - |
1426 | - @property |
1427 | - def unmapped_participants(self): |
1428 | - """See `IPersonViewRestricted`.""" |
1429 | - return Person.select(""" |
1430 | - Person.id = TeamParticipation.person AND |
1431 | - TeamParticipation.team = %s AND |
1432 | - TeamParticipation.person NOT IN ( |
1433 | - SELECT PersonLocation.person |
1434 | - FROM PersonLocation INNER JOIN TeamParticipation ON |
1435 | - PersonLocation.person = TeamParticipation.person |
1436 | - WHERE TeamParticipation.team = %s AND |
1437 | - PersonLocation.latitude IS NOT NULL) AND |
1438 | - Person.teamowner IS NULL |
1439 | - """ % sqlvalues(self.id, self.id), |
1440 | - clauseTables=['TeamParticipation']) |
1441 | - |
1442 | - @property |
1443 | - def unmapped_participants_count(self): |
1444 | - """See `IPersonViewRestricted`.""" |
1445 | - return self.unmapped_participants.count() |
1446 | - |
1447 | @property |
1448 | def open_membership_invitations(self): |
1449 | """See `IPerson`.""" |
1450 | |
1451 | === modified file 'lib/lp/registry/stories/location/personlocation-edit.txt' |
1452 | --- lib/lp/registry/stories/location/personlocation-edit.txt 2012-01-15 13:32:27 +0000 |
1453 | +++ lib/lp/registry/stories/location/personlocation-edit.txt 2012-02-17 12:13:23 +0000 |
1454 | @@ -1,16 +1,15 @@ |
1455 | -Edit person location information |
1456 | -================================ |
1457 | +Edit person time zone information |
1458 | +================================= |
1459 | |
1460 | -A person's location is only editable by people who have launchpad.Edit on |
1461 | +A person's time zone is only editable by people who have launchpad.Edit on |
1462 | the person, which is that person and admins. |
1463 | |
1464 | >>> login('test@canonical.com') |
1465 | >>> zzz = factory.makePerson( |
1466 | - ... name='zzz', time_zone='Africa/Maseru', email='zzz@foo.com', |
1467 | - ... latitude=None, longitude=None) |
1468 | + ... name='zzz', time_zone='Africa/Maseru', email='zzz@foo.com') |
1469 | >>> logout() |
1470 | |
1471 | -A user cannot set another user's location. |
1472 | +A user cannot set another user's +editlocation page. |
1473 | |
1474 | >>> nopriv_browser = setupBrowser(auth="Basic no-priv@canonical.com:test") |
1475 | >>> nopriv_browser.open('http://launchpad.dev/~zzz/+editlocation') |
1476 | @@ -18,35 +17,15 @@ |
1477 | ... |
1478 | Unauthorized:... |
1479 | |
1480 | -A user can set his own location: |
1481 | +A user can set his own time zone: |
1482 | |
1483 | >>> self_browser = setupBrowser(auth="Basic zzz@foo.com:test") |
1484 | >>> self_browser.open('http://launchpad.dev/~zzz/+editlocation') |
1485 | - >>> self_browser.getControl(name='field.location.latitude').value = ( |
1486 | - ... '39.48') |
1487 | - >>> self_browser.getControl(name='field.location.longitude').value = ( |
1488 | - ... '-0.40') |
1489 | - >>> self_browser.getControl(name='field.location.time_zone').value = [ |
1490 | + >>> self_browser.getControl(name='field.time_zone').value = [ |
1491 | ... 'Europe/Madrid'] |
1492 | >>> self_browser.getControl('Update').click() |
1493 | |
1494 | >>> login('zzz@foo.com') |
1495 | - >>> zzz.latitude |
1496 | - 39.4... |
1497 | - >>> zzz.longitude |
1498 | - -0.4... |
1499 | >>> zzz.time_zone |
1500 | u'Europe/Madrid' |
1501 | >>> logout() |
1502 | - |
1503 | -The user can change his location: |
1504 | - |
1505 | - >>> self_browser.open('http://launchpad.dev/~zzz/+editlocation') |
1506 | - >>> self_browser.getControl(name='field.location.latitude').value |
1507 | - '39.48' |
1508 | - |
1509 | -And so can a Launchpad admin: |
1510 | - |
1511 | - >>> admin_browser.open('http://launchpad.dev/~zzz/+editlocation') |
1512 | - >>> admin_browser.getControl(name='field.location.latitude').value |
1513 | - '39.48' |
1514 | |
1515 | === removed file 'lib/lp/registry/stories/location/personlocation.txt' |
1516 | --- lib/lp/registry/stories/location/personlocation.txt 2010-09-21 03:30:43 +0000 |
1517 | +++ lib/lp/registry/stories/location/personlocation.txt 1970-01-01 00:00:00 +0000 |
1518 | @@ -1,18 +0,0 @@ |
1519 | -Person Locations |
1520 | -================ |
1521 | - |
1522 | -People can have a location and time zone in Launchpad. In some cases, a |
1523 | -person has a time zone, but no location. We test that their home page renders |
1524 | -without problems. |
1525 | - |
1526 | - >>> login('test@canonical.com') |
1527 | - >>> zzz = factory.makePerson(name='zzz', time_zone='Africa/Maseru', |
1528 | - ... latitude=None, longitude=None) |
1529 | - >>> logout() |
1530 | - |
1531 | -Any person can see another person's time zone if their is also |
1532 | -location data set, otherwise the location portlet does not show up. |
1533 | - |
1534 | - >>> anon_browser.open('http://launchpad.dev/~zzz') |
1535 | - >>> print extract_text( |
1536 | - ... find_tag_by_id(anon_browser.contents, 'portlet-map')) |
1537 | |
1538 | === removed file 'lib/lp/registry/stories/location/team-map.txt' |
1539 | --- lib/lp/registry/stories/location/team-map.txt 2011-12-28 17:03:06 +0000 |
1540 | +++ lib/lp/registry/stories/location/team-map.txt 1970-01-01 00:00:00 +0000 |
1541 | @@ -1,86 +0,0 @@ |
1542 | -The map of a team's members |
1543 | -=========================== |
1544 | - |
1545 | -The map depends on a stream of XML-formatted data, giving the locations of |
1546 | -all members of the team. We show that this stream works for teams with, and |
1547 | -without, mapped members. |
1548 | - |
1549 | - >>> anon_browser.open('http://launchpad.dev/~guadamen/+mapdata') |
1550 | - >>> print anon_browser.contents |
1551 | - <?xml version="1.0"... |
1552 | - <participants> |
1553 | - <!-- |
1554 | - This collection of team location data is (c) Canonical Ltd and |
1555 | - contributors, and the format may be changed at any time. |
1556 | - --> |
1557 | - <participant |
1558 | - displayname="Colin Watson" |
1559 | - name="kamion" |
1560 | - logo_html="<img alt="" |
1561 | - width="64" height="64" |
1562 | - src="/@@/person-logo" />" |
1563 | - url="/~kamion" |
1564 | - local_time="..." |
1565 | - lat="52.2" |
1566 | - lng="0.3"/> |
1567 | - </participants> |
1568 | - <BLANKLINE> |
1569 | - |
1570 | - >>> anon_browser.open('http://launchpad.dev/~name21/+mapdata') |
1571 | - >>> print anon_browser.contents |
1572 | - <?xml version="1.0"... |
1573 | - <participants> |
1574 | - <!-- |
1575 | - This collection of team location data is (c) Canonical Ltd and |
1576 | - contributors, and the format may be changed at any time. |
1577 | - --> |
1578 | - </participants> |
1579 | - <BLANKLINE> |
1580 | - |
1581 | -The team index page has a map with data limited to 24 members and uses |
1582 | -a slightly different URL. We can see the URL works though there isn't |
1583 | -enough test data to demonstrate the limit but that is done in the view |
1584 | -test. |
1585 | - |
1586 | - >>> anon_browser.open('http://launchpad.dev/~guadamen/+mapdataltd') |
1587 | - >>> print anon_browser.contents |
1588 | - <?xml version="1.0"... |
1589 | - <participants> |
1590 | - <!-- |
1591 | - This collection of team location data is (c) Canonical Ltd and |
1592 | - contributors, and the format may be changed at any time. |
1593 | - --> |
1594 | - <participant |
1595 | - displayname="Colin Watson" |
1596 | - name="kamion" |
1597 | - logo_html="<img alt="" |
1598 | - width="64" height="64" |
1599 | - src="/@@/person-logo" />" |
1600 | - url="/~kamion" |
1601 | - local_time="..." |
1602 | - lat="52.2" |
1603 | - lng="0.3"/> |
1604 | - </participants> |
1605 | - <BLANKLINE> |
1606 | - |
1607 | - |
1608 | -+mapdata |
1609 | --------- |
1610 | - |
1611 | -The display name of all team participants will be escaped to prevent |
1612 | -XSS attacks on any callsite of +mapdata. |
1613 | - |
1614 | - >>> from lp.testing.layers import MemcachedLayer |
1615 | - |
1616 | - >>> admin_browser.open('http://launchpad.dev/~kamion/+edit') |
1617 | - >>> admin_browser.getControl('Display Name').value = ( |
1618 | - ... "<script>alert('Colin \"nasty\"');</script>") |
1619 | - >>> admin_browser.getControl('Save Changes').click() |
1620 | - |
1621 | - >>> MemcachedLayer.purge() |
1622 | - >>> anon_browser.open('http://launchpad.dev/~guadamen/+mapdata') |
1623 | - >>> print anon_browser.contents |
1624 | - <?xml version="1.0"... |
1625 | - ...displayname="&lt;script&gt;alert('Colin |
1626 | - &quot;nasty&quot;');&lt;/script&gt;" |
1627 | - ... |
1628 | |
1629 | === modified file 'lib/lp/registry/stories/person/xx-person-home.txt' |
1630 | --- lib/lp/registry/stories/person/xx-person-home.txt 2011-12-18 13:45:20 +0000 |
1631 | +++ lib/lp/registry/stories/person/xx-person-home.txt 2012-02-17 12:13:23 +0000 |
1632 | @@ -141,12 +141,16 @@ |
1633 | Summary Pagelets |
1634 | ---------------- |
1635 | |
1636 | -A person's homepage also lists Karma information: |
1637 | +A person's homepage also lists Karma and Time zone information: |
1638 | |
1639 | >>> browser.open('http://launchpad.dev/~mark') |
1640 | >>> print extract_text(find_tag_by_id(browser.contents, 'karma')) |
1641 | Karma: 130 Karma help |
1642 | |
1643 | + >>> browser.open('http://launchpad.dev/~kamion') |
1644 | + >>> print extract_text(find_tag_by_id(browser.contents, 'timezone')) |
1645 | + Time zone: Europe/London (UTC+0000) |
1646 | + |
1647 | Negative Ubuntu Code of Conduct signatory status is only displayed for |
1648 | yourself; others won't see it: |
1649 | |
1650 | @@ -173,25 +177,6 @@ |
1651 | 2005-06-06 |
1652 | |
1653 | |
1654 | -Time zones |
1655 | -.......... |
1656 | - |
1657 | -The user's time zone is displayed next to their location details: |
1658 | - |
1659 | - >>> browser.open('http://launchpad.dev/~kamion') |
1660 | - >>> print extract_text( |
1661 | - ... find_tag_by_id(browser.contents, 'portlet-map')) |
1662 | - Location |
1663 | - Time zone: Europe/London... |
1664 | - |
1665 | -If the user does not have location data set then the portlet will not be |
1666 | -shown. |
1667 | - |
1668 | - >>> browser.open('http://launchpad.dev/~bac') |
1669 | - >>> print extract_text( |
1670 | - ... find_tag_by_id(browser.contents, 'portlet-map')) |
1671 | - |
1672 | - |
1673 | Table of contributions |
1674 | ---------------------- |
1675 | |
1676 | |
1677 | === modified file 'lib/lp/registry/stories/webservice/xx-personlocation.txt' |
1678 | --- lib/lp/registry/stories/webservice/xx-personlocation.txt 2009-10-01 12:30:32 +0000 |
1679 | +++ lib/lp/registry/stories/webservice/xx-personlocation.txt 2012-02-17 12:13:23 +0000 |
1680 | @@ -1,7 +1,9 @@ |
1681 | = Person location = |
1682 | |
1683 | The location of a person is readable through the Web Service API, and can |
1684 | -be set that way, too. |
1685 | +be set that way too, but it has been deprecated as we no longer have that |
1686 | +information in our database, so the latitude/longitude will always be None. |
1687 | +The time_zone attribute has not been deprecated, though. |
1688 | |
1689 | We start with the case where there is no information about the user's |
1690 | location, at all. |
1691 | @@ -14,30 +16,8 @@ |
1692 | >>> print jdub['longitude'] |
1693 | None |
1694 | |
1695 | -We show that we handle the case where there is no latitude or |
1696 | -longitude information. |
1697 | - |
1698 | - >>> marilize = webservice.get("/~marilize").jsonBody() |
1699 | - >>> print marilize['time_zone'] |
1700 | - Africa/Maseru |
1701 | - >>> print marilize['latitude'] |
1702 | - None |
1703 | - >>> print marilize['longitude'] |
1704 | - None |
1705 | - |
1706 | -And here is an example, where we have location and time zone information for |
1707 | -a user: |
1708 | - |
1709 | - >>> kamion = webservice.get("/~kamion").jsonBody() |
1710 | - >>> print kamion['time_zone'] |
1711 | - Europe/London |
1712 | - >>> print kamion['latitude'] |
1713 | - 52.2 |
1714 | - >>> print kamion['longitude'] |
1715 | - 0.3 |
1716 | - |
1717 | -It is also possible to set the location, if it is not yet locked by being |
1718 | -provided by the user themselves. |
1719 | +It is also possible to set the location, but as you can see the |
1720 | +latitude/longitude read via the Web API will still be None. |
1721 | |
1722 | >>> print webservice.get("/~jdub").jsonBody()['time_zone'] |
1723 | None |
1724 | @@ -48,3 +28,7 @@ |
1725 | ... |
1726 | >>> webservice.get("/~jdub").jsonBody()['time_zone'] |
1727 | u'Australia/Sydney' |
1728 | + >>> print jdub['latitude'] |
1729 | + None |
1730 | + >>> print jdub['longitude'] |
1731 | + None |
1732 | |
1733 | === modified file 'lib/lp/registry/templates/person-index.pt' |
1734 | --- lib/lp/registry/templates/person-index.pt 2011-06-30 12:58:22 +0000 |
1735 | +++ lib/lp/registry/templates/person-index.pt 2012-02-17 12:13:23 +0000 |
1736 | @@ -96,8 +96,6 @@ |
1737 | |
1738 | </div> |
1739 | </div> |
1740 | - |
1741 | - <div tal:content="structure context/@@+portlet-map" /> |
1742 | </tal:is-valid-person> |
1743 | |
1744 | <div id="not-lp-user-or-team" |
1745 | |
1746 | === modified file 'lib/lp/registry/templates/person-portlet-contact-details.pt' |
1747 | --- lib/lp/registry/templates/person-portlet-contact-details.pt 2011-11-26 04:03:29 +0000 |
1748 | +++ lib/lp/registry/templates/person-portlet-contact-details.pt 2012-02-17 12:13:23 +0000 |
1749 | @@ -169,7 +169,7 @@ |
1750 | </dd> |
1751 | </dl> |
1752 | |
1753 | - <dl tal:condition="context/time_zone"> |
1754 | + <dl id="timezone" tal:condition="context/time_zone"> |
1755 | <dt>Time zone: |
1756 | <a tal:replace="structure overview_menu/editlocation/fmt:icon" /> |
1757 | </dt> |
1758 | |
1759 | === removed file 'lib/lp/registry/templates/person-portlet-map.pt' |
1760 | --- lib/lp/registry/templates/person-portlet-map.pt 2010-09-21 03:30:43 +0000 |
1761 | +++ lib/lp/registry/templates/person-portlet-map.pt 1970-01-01 00:00:00 +0000 |
1762 | @@ -1,21 +0,0 @@ |
1763 | -<tal:root |
1764 | - xmlns:tal="http://xml.zope.org/namespaces/tal" |
1765 | - xmlns:metal="http://xml.zope.org/namespaces/metal" |
1766 | - xmlns:i18n="http://xml.zope.org/namespaces/i18n" |
1767 | - omit-tag=""> |
1768 | - |
1769 | -<div class="portlet" id="portlet-map" |
1770 | - tal:define="overview_menu context/menu:overview"> |
1771 | - |
1772 | - <tal:show-map condition="view/should_show_map_portlet"> |
1773 | - <h2>Location</h2> |
1774 | - |
1775 | - <div tal:condition="context/time_zone"> |
1776 | - <strong>Time zone:</strong> |
1777 | - <span tal:replace="context/time_zone">UTC</span> |
1778 | - <a tal:replace="structure overview_menu/editlocation/fmt:icon" /> |
1779 | - </div> |
1780 | - </tal:show-map> |
1781 | - |
1782 | -</div> |
1783 | -</tal:root> |
1784 | |
1785 | === removed file 'lib/lp/registry/templates/team-map-data.pt' |
1786 | --- lib/lp/registry/templates/team-map-data.pt 2010-06-24 20:22:57 +0000 |
1787 | +++ lib/lp/registry/templates/team-map-data.pt 1970-01-01 00:00:00 +0000 |
1788 | @@ -1,17 +0,0 @@ |
1789 | -<?xml version="1.0"?><!--*- mode: nxml -*--> |
1790 | -<participants xmlns:tal="http://xml.zope.org/namespaces/tal" |
1791 | - tal:content="cache:public, 30 minute"> |
1792 | - <!-- |
1793 | - This collection of team location data is (c) Canonical Ltd and |
1794 | - contributors, and the format may be changed at any time. |
1795 | - --> |
1796 | - <participant tal:repeat="participant view/mapped_participants" |
1797 | - tal:attributes=" |
1798 | - lat participant/latitude; |
1799 | - lng participant/longitude; |
1800 | - displayname participant/fmt:displayname/fmt:escape; |
1801 | - local_time participant/fmt:local-time; |
1802 | - logo_html participant/image:logo; |
1803 | - url participant/fmt:url; |
1804 | - name participant/name" /> |
1805 | -</participants> |
1806 | |
1807 | === removed file 'lib/lp/registry/templates/team-map.pt' |
1808 | --- lib/lp/registry/templates/team-map.pt 2010-05-17 17:29:08 +0000 |
1809 | +++ lib/lp/registry/templates/team-map.pt 1970-01-01 00:00:00 +0000 |
1810 | @@ -1,45 +0,0 @@ |
1811 | -<html |
1812 | - xmlns="http://www.w3.org/1999/xhtml" |
1813 | - xmlns:tal="http://xml.zope.org/namespaces/tal" |
1814 | - xmlns:metal="http://xml.zope.org/namespaces/metal" |
1815 | - xmlns:i18n="http://xml.zope.org/namespaces/i18n" |
1816 | - metal:use-macro="view/macro:page/main_only" |
1817 | - i18n:domain="launchpad" |
1818 | -> |
1819 | - |
1820 | -<body> |
1821 | - |
1822 | -<div metal:fill-slot="main"> |
1823 | - |
1824 | - <div style="height:420px;"> |
1825 | - <div tal:condition="not: view/has_mapped_participants"> |
1826 | - None of the current participants in |
1827 | - <tal:teamname replace="context/fmt:displayname" /> have a recorded |
1828 | - location. |
1829 | - </div> |
1830 | - |
1831 | - <div |
1832 | - tal:content="cache:public, 1 hour"> |
1833 | - <tal:some_mapped condition="view/has_mapped_participants"> |
1834 | - <div id="team_map_div" |
1835 | - style="width: 75%; height: 250px; border: 1px; float: left; |
1836 | - margin: 0 14px 0 0;"> |
1837 | - </div> |
1838 | - <tal:map replace="structure view/map_html" /> |
1839 | - </tal:some_mapped> |
1840 | - |
1841 | - <div tal:condition="view/has_mapped_participants" style="float:left;"> |
1842 | - <div style="margin-bottom: 4px;"> |
1843 | - <strong>Members' current local time:</strong> |
1844 | - </div> |
1845 | - <tal:time_set repeat="time view/times"> |
1846 | - <tal:times replace="time" /><br /> |
1847 | - </tal:time_set> |
1848 | - </div> |
1849 | - </div> |
1850 | - </div> |
1851 | - |
1852 | -</div> |
1853 | - |
1854 | -</body> |
1855 | -</html> |
1856 | |
1857 | === removed file 'lib/lp/registry/templates/team-portlet-map.pt' |
1858 | --- lib/lp/registry/templates/team-portlet-map.pt 2010-09-21 04:21:16 +0000 |
1859 | +++ lib/lp/registry/templates/team-portlet-map.pt 1970-01-01 00:00:00 +0000 |
1860 | @@ -1,37 +0,0 @@ |
1861 | -<tal:root |
1862 | - xmlns:tal="http://xml.zope.org/namespaces/tal" |
1863 | - xmlns:metal="http://xml.zope.org/namespaces/metal" |
1864 | - xmlns:i18n="http://xml.zope.org/namespaces/i18n" |
1865 | - omit-tag=""> |
1866 | - |
1867 | -<div class="portlet" id="portlet-map" style="margin-bottom: 0px;" |
1868 | - tal:define="link context/menu:overview/map"> |
1869 | -<table> |
1870 | -<tr><td> |
1871 | - <h2> |
1872 | - <span class="see-all"> |
1873 | - <a tal:attributes="href link/fmt:url">View map and time zones</a> |
1874 | - </span> |
1875 | - Location |
1876 | - </h2> |
1877 | - |
1878 | - <tal:can_view condition="context/required:launchpad.View"> |
1879 | - <div style="width: 400px;" tal:condition="view/has_mapped_participants"> |
1880 | - <div id="team_map_actions" |
1881 | - style="position:relative; z-index: 9999; |
1882 | - float:right; width: 8.5em; margin: 2px; |
1883 | - background-color: white; padding-bottom:1px;"></div> |
1884 | - <div id="team_map_div" class="small-map" |
1885 | - style="height: 200px; border: 1px; margin-top: 4px;"></div> |
1886 | - <tal:mapscript replace="structure view/map_portlet_html" /> |
1887 | - </div> |
1888 | - |
1889 | - <tal:no-map tal:condition="not: view/has_mapped_participants"> |
1890 | - <a tal:attributes="href link/target" |
1891 | - ><img src="/+icing/portlet-map-unknown.png" /></a> |
1892 | - </tal:no-map> |
1893 | - </tal:can_view> |
1894 | -</td></tr> |
1895 | -</table> |
1896 | -</div> |
1897 | -</tal:root> |
1898 | |
1899 | === modified file 'lib/lp/registry/tests/test_person.py' |
1900 | --- lib/lp/registry/tests/test_person.py 2012-02-16 08:27:37 +0000 |
1901 | +++ lib/lp/registry/tests/test_person.py 2012-02-17 12:13:23 +0000 |
1902 | @@ -718,8 +718,7 @@ |
1903 | 'all_members_prepopulated', 'approvedmembers', |
1904 | 'deactivatedmembers', 'expiredmembers', 'inactivemembers', |
1905 | 'invited_members', 'member_memberships', 'pendingmembers', |
1906 | - 'proposedmembers', 'unmapped_participants', 'longitude', |
1907 | - 'latitude', 'time_zone', |
1908 | + 'proposedmembers', 'time_zone', |
1909 | ) |
1910 | snap = Snapshot(self.myteam, providing=providedBy(self.myteam)) |
1911 | for name in omitted: |
1912 | |
1913 | === modified file 'lib/lp/registry/tests/test_personset.py' |
1914 | --- lib/lp/registry/tests/test_personset.py 2012-02-09 20:23:49 +0000 |
1915 | +++ lib/lp/registry/tests/test_personset.py 2012-02-17 12:13:23 +0000 |
1916 | @@ -32,7 +32,6 @@ |
1917 | from lp.registry.interfaces.person import ( |
1918 | IPersonSet, |
1919 | PersonCreationRationale, |
1920 | - PersonVisibility, |
1921 | TeamEmailAddressError, |
1922 | ) |
1923 | from lp.registry.interfaces.personnotification import IPersonNotificationSet |
1924 | @@ -140,7 +139,7 @@ |
1925 | person.is_valid_person |
1926 | person.karma |
1927 | person.is_ubuntu_coc_signer |
1928 | - person.location |
1929 | + person.location, |
1930 | person.archive |
1931 | person.preferredemail |
1932 | self.assertThat(recorder, HasQueryCount(LessThan(1))) |
1933 | |
1934 | === modified file 'lib/lp/registry/xmlrpc/canonicalsso.py' |
1935 | --- lib/lp/registry/xmlrpc/canonicalsso.py 2012-02-14 09:28:26 +0000 |
1936 | +++ lib/lp/registry/xmlrpc/canonicalsso.py 2012-02-17 12:13:23 +0000 |
1937 | @@ -38,7 +38,7 @@ |
1938 | if person is None: |
1939 | return |
1940 | |
1941 | - time_zone = person.location and person.location.time_zone |
1942 | + time_zone = person.time_zone |
1943 | team_names = dict( |
1944 | (removeSecurityProxy(t).name, t.private) |
1945 | for t in person.teams_participated_in) |
This looks ok to land. Please file a bug and link the branch to it so that it can be qa'ed.