Merge lp:~salgado/launchpad/remove-map-views into lp:launchpad

Proposed by Guilherme Salgado
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
Reviewer Review Type Date Requested Status
Ian Booth (community) Approve
Review via email: mp+93509@code.launchpad.net

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.setLocation() but that would have performance implications (or so some tests tell me) so I left them in even though the former is only used to get the time zone and the latter only used to set it.

To post a comment you must log in.
Revision history for this message
Ian Booth (wallyworld) wrote :

This looks ok to land. Please file a bug and link the branch to it so that it can be qa'ed.

review: Approve
Revision history for this message
Guilherme Salgado (salgado) wrote :

Thanks for the review, Ian. I've linked a bug to my branch

Revision history for this message
Guilherme Salgado (salgado) wrote :

There was a test which was still using setLocationVisibility() so this didn't land. I've pushed a fix for it and now it should be good to go.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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- ... "&lt;script&gt;alert('John &quot;nasty&quot; O'Brien');&lt;/script&gt;",...
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="&lt;img alt=&quot;&quot;
1561- width=&quot;64&quot; height=&quot;64&quot;
1562- src=&quot;/@@/person-logo&quot; /&gt;"
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="&lt;img alt=&quot;&quot;
1598- width=&quot;64&quot; height=&quot;64&quot;
1599- src=&quot;/@@/person-logo&quot; /&gt;"
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="&amp;lt;script&amp;gt;alert('Colin
1626- &amp;quot;nasty&amp;quot;');&amp;lt;/script&amp;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)