launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #06404
[Merge] lp:~salgado/launchpad/remove-map-views into lp:launchpad
Guilherme Salgado has proposed merging lp:~salgado/launchpad/remove-map-views into lp:launchpad.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~salgado/launchpad/remove-map-views/+merge/93509
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.
--
https://code.launchpad.net/~salgado/launchpad/remove-map-views/+merge/93509
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~salgado/launchpad/remove-map-views into lp:launchpad.
=== removed file 'lib/lp/app/widgets/doc/location-widget.txt'
--- lib/lp/app/widgets/doc/location-widget.txt 2012-02-02 12:30:53 +0000
+++ lib/lp/app/widgets/doc/location-widget.txt 1970-01-01 00:00:00 +0000
@@ -1,204 +0,0 @@
-Location widget
-===============
-
-A widget used when setting the geographic location of a given person.
-
- >>> from lp.services.webapp.servers import LaunchpadTestRequest
- >>> from lp.app.widgets.location import LocationWidget
- >>> from lp.registry.interfaces.person import IPersonSet
- >>> from lp.services.fields import LocationField
- >>> salgado = getUtility(IPersonSet).getByName('salgado')
- >>> field = LocationField(__name__='location', title=u'Location')
-
- >>> bound_field = field.bind(salgado)
- >>> request = LaunchpadTestRequest(
- ... # Let's pretend requests are coming from Brazil.
- ... environ={'REMOTE_ADDR': '201.13.165.145'})
-
-When rendering salgado's location widget to himself, we'll center the
-map around the location where he seems to be, based on the IP address of
-the request.
-
- >>> login_person(salgado)
- >>> widget = LocationWidget(bound_field, request)
- >>> widget.zoom
- 7
- >>> widget.center_lat
- -23...
- >>> widget.center_lng
- -45...
-
-Since salgado's location hasn't been specified yet, there'll be no
-visual marker indicating where he is in the map.
-
- >>> widget.show_marker
- 0
-
-If someone else sees salgado's location widget, though, we have no way
-of guessing what coordinates to center the map around (because the
-request doesn't come from salgado's browser), so we won't try to do so.
-
- >>> login(ANONYMOUS)
- >>> widget = LocationWidget(bound_field, request)
- >>> widget.zoom
- 2
- >>> widget.center_lat
- 15.0
- >>> widget.center_lng
- 0.0
- >>> widget.show_marker
- 0
-
-When rendering the location widget of someone who already specified a
-location, we'll obviously center the map around that, but we'll also put
-a visual marker in the latitude/longitude specified as that person's
-location.
-
- >>> kamion = getUtility(IPersonSet).getByName('kamion')
- >>> bound_field = field.bind(kamion)
- >>> login_person(kamion)
- >>> widget = LocationWidget(bound_field, request)
- >>> widget.zoom
- 9
- >>> widget.center_lat
- 52...
-
-This next test fails in ec2 but passes locally. On ec2, the printed value
-was 0.2999999. The round() should have fixed that. Testing shows that
-round() is broken in Python 2.6 and works in 2.7
-We'll disable this for now and fix in a cleanup branch.
-
-round(widget.center_lng, 5)
-0.3...
-
- >>> widget.show_marker
- 1
-
-The widget's getInputValue() method will return a LocationValue object,
-which stored the geographic coordinates and the timezone.
-
- >>> request = LaunchpadTestRequest(
- ... form={'field.location.time_zone': 'Europe/London',
- ... 'field.location.latitude': '51.3',
- ... 'field.location.longitude': '0.32'})
- >>> widget = LocationWidget(bound_field, request)
- >>> widget.hasInput()
- True
- >>> location = widget.getInputValue()
- >>> location
- <lp.app.widgets.location.LocationValue...
- >>> location.latitude # Only the integral part due to rounding errors.
- 51...
- >>> location.longitude
- 0.32...
- >>> location.time_zone
- 'Europe/London'
-
-If we try to set only the latitude *or* longitude, but not both, we'll
-get an error.
-
- >>> request = LaunchpadTestRequest(
- ... form={'field.location.time_zone': 'Europe/London',
- ... 'field.location.longitude': '0.32'})
- >>> widget = LocationWidget(bound_field, request)
- >>> widget.hasInput()
- True
- >>> widget.getInputValue()
- Traceback (most recent call last):
- ...
- WidgetInputError:...Please provide both latitude and longitude...
-
-We also get errors if we don't specify a time zone or if the
-latitude/longitude have non-realistic values.
-
- >>> request = LaunchpadTestRequest(
- ... form={'field.location.latitude': '51.3',
- ... 'field.location.longitude': '0.32'})
- >>> widget = LocationWidget(bound_field, request)
- >>> widget.hasInput()
- True
- >>> location = widget.getInputValue()
- Traceback (most recent call last):
- ...
- MissingInputError: ('field.location.time_zone'...
-
- >>> request = LaunchpadTestRequest(
- ... form={'field.location.time_zone': 'Europe/London',
- ... 'field.location.latitude': '99.3',
- ... 'field.location.longitude': '0.32'})
- >>> widget = LocationWidget(bound_field, request)
- >>> widget.hasInput()
- True
- >>> widget.getInputValue()
- Traceback (most recent call last):
- ...
- WidgetInputError:...Please provide a more realistic latitude and
- longitude...
-
-
-The widget's HTML
------------------
-
-The widget's HTML will include <input> elements for the latitude,
-longitude and time zone fields. The values of these elements will be
-set from within Javascript whenever the user changes the location on
-the map.
-
- >>> bound_field = field.bind(kamion)
- >>> request = LaunchpadTestRequest(
- ... environ={'REMOTE_ADDR': '201.13.165.145'})
- >>> login_person(kamion)
- >>> widget = LocationWidget(bound_field, request)
- >>> print widget()
- <input...name="field.location.latitude"...type="hidden"...
- <input...name="field.location.longitude"...type="hidden"...
- <select...name="field.location.time_zone"...
-
-
-The widget's script
--------------------
-
-The widget initializes the mapping.renderPersonMap() JavaScript methods
-with its map_javascript property. The widget uses the
-mapping.setLocation() method to lookup the time zone for the location.
-The launchpad.geonames_identity config key provides the identity required to
-access ba-ws.geonames.net's service.
-
- >>> from lp.services.config import config
-
- >>> config.push('geoname_data', """
- ... [launchpad]
- ... geonames_identity: totoro
- ... """)
- >>> print widget.map_javascript
- <BLANKLINE>
- <script type="text/javascript">
- LPJS.use('node', 'lp.app.mapping', function(Y) {
- function renderMap() {
- Y.lp.app.mapping.renderPersonMap(
- 52.2, 0.3, "Colin Watson",
- 'kamion', '<img ... />', 'totoro',
- 'field.location.latitude', 'field.location.longitude',
- 'field.location.time_zone', 9, 1);
- }
- Y.on("domready", renderMap);
- });
- </script>
-
- # Restore the config the the default state.
- >>> config_data = config.pop('geoname_data')
-
-
-XSS
----
-
-The widget must escape and JS encode the person's displayname to prevent
-XSS attacks and to make sure the generated javascript can be parsed.
-
- >>> kamion.displayname = (
- ... "<script>alert('John \"nasty\" O\'Brien');</script>")
- >>> bound_field = field.bind(kamion)
- >>> widget = LocationWidget(bound_field, request)
- >>> print widget.map_javascript
- <BLANKLINE>
- ... "<script>alert('John "nasty" O'Brien');</script>",...
=== removed file 'lib/lp/app/widgets/location.py'
--- lib/lp/app/widgets/location.py 2012-02-01 15:46:43 +0000
+++ lib/lp/app/widgets/location.py 1970-01-01 00:00:00 +0000
@@ -1,192 +0,0 @@
-# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
-# GNU Affero General Public License version 3 (see the file LICENSE).
-
-# pylint: disable-msg=E0702
-
-__metaclass__ = type
-__all__ = [
- 'ILocationWidget',
- 'LocationWidget',
- ]
-
-from lazr.restful.utils import safe_js_escape
-from z3c.ptcompat import ViewPageTemplateFile
-from zope.app.form import InputWidget
-from zope.app.form.browser.interfaces import IBrowserWidget
-from zope.app.form.browser.widget import BrowserWidget
-from zope.app.form.interfaces import (
- IInputWidget,
- WidgetInputError,
- )
-from zope.component import getUtility
-from zope.formlib import form
-from zope.interface import implements
-from zope.schema import (
- Choice,
- Float,
- )
-
-from lp import _
-from lp.app.browser.tales import ObjectImageDisplayAPI
-from lp.app.validators import LaunchpadValidationError
-from lp.registry.interfaces.location import IObjectWithLocation
-from lp.services.config import config
-from lp.services.geoip.interfaces import IGeoIPRecord
-from lp.services.webapp.interfaces import (
- ILaunchBag,
- IMultiLineWidgetLayout,
- )
-
-
-class ILocationWidget(IInputWidget, IBrowserWidget, IMultiLineWidgetLayout):
- """A widget for selecting a location and time zone."""
-
-
-class LocationValue:
- """A location passed back from a LocationWidget.
-
- This is a single object which contains the latitude, longitude and time
- zone of the location.
- """
-
- def __init__(self, latitude, longitude, time_zone):
- self.latitude = latitude
- self.longitude = longitude
- self.time_zone = time_zone
-
-
-class LocationWidget(BrowserWidget, InputWidget):
- """See `ILocationWidget`."""
- implements(ILocationWidget)
-
- __call__ = ViewPageTemplateFile("templates/location.pt")
-
- def __init__(self, context, request):
- super(LocationWidget, self).__init__(context, request)
- fields = form.Fields(
- Float(__name__='latitude', title=_('Latitude'), required=False),
- Float(__name__='longitude', title=_('Longitude'), required=False),
- Choice(
- __name__='time_zone', vocabulary='TimezoneName',
- title=_('Time zone'), required=True,
- description=_(
- 'Once the time zone is correctly set, events '
- 'in Launchpad will be displayed in local time.')))
- # This will be the initial zoom level and center of the map.
- self.zoom = 2
- self.center_lat = 15.0
- self.center_lng = 0.0
- # By default, we will not show a marker initially, because we are
- # not absolutely certain of the location we are proposing. The
- # variable is a number that will be passed to JavaScript and
- # evaluated as a boolean.
- self.show_marker = 0
- data = {
- 'time_zone': None,
- 'latitude': None,
- 'longitude': None,
- }
- # If we are creating a record for ourselves, then we will default to
- # a location GeoIP guessed, and a higher zoom.
- if getUtility(ILaunchBag).user == context.context:
- geo_request = IGeoIPRecord(request)
- self.zoom = 7
- self.center_lat = geo_request.latitude
- self.center_lng = geo_request.longitude
- data['time_zone'] = geo_request.time_zone
- current_location = IObjectWithLocation(self.context.context)
- if current_location.latitude is not None:
- # We are updating a location.
- data['latitude'] = current_location.latitude
- data['longitude'] = current_location.longitude
- self.center_lat = current_location.latitude
- self.center_lng = current_location.longitude
- self.zoom = 9
- self.show_marker = 1
- if current_location.time_zone is not None:
- # We are updating a time zone.
- data['time_zone'] = current_location.time_zone
- self.initial_values = data
- widgets = form.setUpWidgets(
- fields, self.name, context, request, ignore_request=False,
- data=data)
- self.time_zone_widget = widgets['time_zone']
- self.latitude_widget = widgets['latitude']
- self.longitude_widget = widgets['longitude']
-
- @property
- def map_javascript(self):
- """The Javascript code necessary to render the map."""
- person = self.context.context
- replacements = dict(
- center_lat=self.center_lat,
- center_lng=self.center_lng,
- displayname=safe_js_escape(person.displayname),
- name=person.name,
- logo_html=ObjectImageDisplayAPI(person).logo(),
- geoname=config.launchpad.geonames_identity,
- lat_name=self.latitude_widget.name,
- lng_name=self.longitude_widget.name,
- tz_name=self.time_zone_widget.name,
- zoom=self.zoom,
- show_marker=self.show_marker)
- return """
- <script type="text/javascript">
- LPJS.use('node', 'lp.app.mapping', function(Y) {
- function renderMap() {
- Y.lp.app.mapping.renderPersonMap(
- %(center_lat)s, %(center_lng)s, %(displayname)s,
- '%(name)s', '%(logo_html)s', '%(geoname)s',
- '%(lat_name)s', '%(lng_name)s', '%(tz_name)s',
- %(zoom)s, %(show_marker)s);
- }
- Y.on("domready", renderMap);
- });
- </script>
- """ % replacements
-
- def hasInput(self):
- """See `IBrowserWidget`.
-
- Return True if time zone or latitude widgets have input.
- """
- return (self.time_zone_widget.hasInput()
- or self.latitude_widget.hasInput())
-
- def getInputValue(self):
- """See `IBrowserWidget`.
-
- Return a `LocationValue` object containing the latitude, longitude and
- time zone chosen.
- """
- self._error = None
- time_zone = self.time_zone_widget.getInputValue()
- latitude = None
- longitude = None
- if self.latitude_widget.hasInput():
- latitude = self.latitude_widget.getInputValue()
- if self.longitude_widget.hasInput():
- longitude = self.longitude_widget.getInputValue()
- if time_zone is None:
- self._error = WidgetInputError(
- self.name, self.label,
- LaunchpadValidationError(
- _('Please provide a valid time zone.')))
- raise self._error
- if ((latitude is None and longitude is not None)
- or (latitude is not None and longitude is None)):
- # We must receive both a latitude and a longitude.
- self._error = WidgetInputError(
- self.name, self.label,
- LaunchpadValidationError(
- _('Please provide both latitude and longitude.')))
- raise self._error
- if latitude is not None:
- if abs(latitude) > 90 or abs(longitude) > 180:
- # We need latitude and longitude within range.
- self._error = WidgetInputError(
- self.name, self.label, LaunchpadValidationError(
- _('Please provide a more realistic latitude '
- 'and longitude.')))
- raise self._error
- return LocationValue(latitude, longitude, time_zone)
=== modified file 'lib/lp/app/widgets/templates/location.pt'
--- lib/lp/app/widgets/templates/location.pt 2010-09-21 03:30:43 +0000
+++ lib/lp/app/widgets/templates/location.pt 2012-02-16 23:29:53 +0000
@@ -2,8 +2,6 @@
xmlns:tal="http://xml.zope.org/namespaces/tal"
xmlns:i18n="http://xml.zope.org/namespaces/i18n"
omit-tag="">
- <tal:latitude replace="structure view/latitude_widget/hidden" />
- <tal:longitude replace="structure view/longitude_widget/hidden" />
<div>
<tal:time-zone replace="structure view/time_zone_widget" />
</div>
=== modified file 'lib/lp/registry/browser/configure.zcml'
--- lib/lp/registry/browser/configure.zcml 2012-02-13 21:48:25 +0000
+++ lib/lp/registry/browser/configure.zcml 2012-02-16 23:29:53 +0000
@@ -841,7 +841,7 @@
<browser:page
name="+editlocation"
for="lp.registry.interfaces.person.IPerson"
- class="lp.registry.browser.person.PersonEditLocationView"
+ class="lp.registry.browser.person.PersonEditTimeZoneView"
permission="launchpad.Edit"
template="../../app/templates/generic-edit.pt"/>
<browser:page
@@ -946,9 +946,6 @@
<browser:page
name="+xrds"
attribute="xrds"/>
- <browser:page
- name="+portlet-map"
- template="../templates/person-portlet-map.pt"/>
</browser:pages>
<browser:page
for="lp.registry.interfaces.person.IPerson"
@@ -1070,12 +1067,6 @@
template="../templates/team-portlet-polls.pt"/>
<browser:page
for="lp.registry.interfaces.person.ITeam"
- permission="zope.Public"
- class="lp.registry.browser.team.TeamMapView"
- name="+map"
- template="../templates/team-map.pt"/>
- <browser:page
- for="lp.registry.interfaces.person.ITeam"
class="lp.registry.browser.team.TeamMailingListSubscribersView"
permission="zope.Public"
name="+mailing-list-subscribers"
@@ -1083,18 +1074,6 @@
<browser:page
for="lp.registry.interfaces.person.ITeam"
permission="zope.Public"
- class="lp.registry.browser.team.TeamMapData"
- template="../templates/team-map-data.pt"
- name="+mapdata"/>
- <browser:page
- for="lp.registry.interfaces.person.ITeam"
- permission="zope.Public"
- class="lp.registry.browser.team.TeamMapLtdData"
- template="../templates/team-map-data.pt"
- name="+mapdataltd"/>
- <browser:page
- for="lp.registry.interfaces.person.ITeam"
- permission="zope.Public"
name="+listing-simple"
template="../templates/team-listing-simple.pt"/>
<browser:page
@@ -1105,12 +1084,6 @@
class="lp.registry.browser.team.TeamMugshotView"/>
<browser:page
for="lp.registry.interfaces.person.ITeam"
- class="lp.registry.browser.team.TeamMapLtdView"
- permission="zope.Public"
- name="+portlet-map"
- template="../templates/team-portlet-map.pt"/>
- <browser:page
- for="lp.registry.interfaces.person.ITeam"
class="lp.registry.browser.team.TeamIndexView"
permission="zope.Public"
name="+portlet-membership"
=== modified file 'lib/lp/registry/browser/person.py'
--- lib/lp/registry/browser/person.py 2012-02-14 15:32:31 +0000
+++ lib/lp/registry/browser/person.py 2012-02-16 23:29:53 +0000
@@ -26,7 +26,7 @@
'PersonEditHomePageView',
'PersonEditIRCNicknamesView',
'PersonEditJabberIDsView',
- 'PersonEditLocationView',
+ 'PersonEditTimeZoneView',
'PersonEditSSHKeysView',
'PersonEditView',
'PersonFacets',
@@ -114,7 +114,6 @@
from zope.interface.interface import invariant
from zope.publisher.interfaces import NotFound
from zope.schema import (
- Bool,
Choice,
Text,
TextLine,
@@ -155,7 +154,6 @@
LaunchpadRadioWidget,
LaunchpadRadioWidgetWithDescription,
)
-from lp.app.widgets.location import LocationWidget
from lp.blueprints.browser.specificationtarget import HasSpecificationsView
from lp.blueprints.enums import SpecificationFilter
from lp.bugs.browser.bugtask import BugTaskSearchListingView
@@ -222,7 +220,6 @@
from lp.services.config import config
from lp.services.database.sqlbase import flush_database_updates
from lp.services.feeds.browser import FeedsMixin
-from lp.services.fields import LocationField
from lp.services.geoip.interfaces import IRequestPreferredLanguages
from lp.services.gpg.interfaces import (
GPGKeyNotFoundError,
@@ -3024,47 +3021,6 @@
"mailing list."))
self.request.response.redirect(canonical_url(self.context))
- @property
- def map_portlet_html(self):
- """Generate the HTML which shows the map portlet."""
- assert self.has_visible_location, (
- "Can't generate the map for a person who hasn't set a "
- "visible location.")
- replacements = {'center_lat': self.context.latitude,
- 'center_lng': self.context.longitude}
- return u"""
- <script type="text/javascript">
- LPJS.use('node', 'lp.app.mapping', function(Y) {
- function renderMap() {
- Y.lp.app.mapping.renderPersonMapSmall(
- %(center_lat)s, %(center_lng)s);
- }
- Y.on("domready", renderMap);
- });
- </script>""" % replacements
-
- @cachedproperty
- def has_visible_location(self):
- """Does the person have latitude and a visible location."""
- if self.context.is_team:
- return self.context.mapped_participants_count > 0
- else:
- return (check_permission('launchpad.View', self.context.location)
- and self.context.latitude is not None)
-
- @property
- def should_show_map_portlet(self):
- """Should the map portlet be displayed?
-
- The map portlet is displayed only if the person has no location
- specified (latitude), or if the user has permission to view the
- person's location.
- """
- if self.user == self.context:
- return True
- else:
- return self.has_visible_location
-
class PersonCodeOfConductEditView(LaunchpadView):
"""View for the ~person/+codesofconduct pages."""
@@ -4762,22 +4718,19 @@
canonical_url(self.context, view_name='+oauth-tokens'))
-class PersonLocationForm(Interface):
-
- location = LocationField(
- title=_('Time zone'),
- required=True)
- hide = Bool(
- title=_("Hide my location details from others."),
- required=True, default=False)
-
-
-class PersonEditLocationView(LaunchpadFormView):
- """Edit a person's location."""
-
- schema = PersonLocationForm
- field_names = ['location']
- custom_widget('location', LocationWidget)
+class PersonTimeZoneForm(Interface):
+
+ time_zone = Choice(
+ vocabulary='TimezoneName', title=_('Time zone'), required=True,
+ description=_(
+ 'Once the time zone is correctly set, events '
+ 'in Launchpad will be displayed in local time.'))
+
+
+class PersonEditTimeZoneView(LaunchpadFormView):
+ """Edit a person's time zone."""
+
+ schema = PersonTimeZoneForm
page_title = label = 'Set timezone'
@property
@@ -4788,17 +4741,14 @@
@action(_("Update"), name="update")
def action_update(self, action, data):
- """Set the coordinates and time zone for the person."""
- new_location = data.get('location')
- if new_location is None:
+ """Set the time zone for the person."""
+ timezone = data.get('time_zone')
+ if timezone is None:
raise UnexpectedFormData('No location received.')
- latitude = new_location.latitude
- longitude = new_location.longitude
- time_zone = new_location.time_zone
- self.context.setLocation(latitude, longitude, time_zone, self.user)
- if 'hide' in self.field_names:
- visible = not data['hide']
- self.context.setLocationVisibility(visible)
+ # XXX salgado, 2012-02-16, bug=933699: Use setLocation() because it's
+ # the cheaper way to set the timezone of a person. Once the bug is
+ # fixed we'll be able to get rid of this hack.
+ self.context.setLocation(None, None, timezone, self.user)
def archive_to_person(archive):
=== modified file 'lib/lp/registry/browser/team.py'
--- lib/lp/registry/browser/team.py 2012-02-16 03:12:17 +0000
+++ lib/lp/registry/browser/team.py 2012-02-16 23:29:53 +0000
@@ -20,10 +20,6 @@
'TeamMailingListModerationView',
'TeamMailingListSubscribersView',
'TeamMailingListArchiveView',
- 'TeamMapData',
- 'TeamMapLtdData',
- 'TeamMapView',
- 'TeamMapLtdView',
'TeamMemberAddView',
'TeamMembershipView',
'TeamMugshotView',
@@ -1206,112 +1202,6 @@
self.widgets['newmember'].setRenderedValue(None)
-class TeamMapView(LaunchpadView):
- """Show all people with known locations on a map.
-
- Also provides links to edit the locations of people in the team without
- known locations.
- """
-
- label = "Team member locations"
- limit = None
-
- @cachedproperty
- def mapped_participants(self):
- """Participants with locations."""
- return self.context.getMappedParticipants(limit=self.limit)
-
- @cachedproperty
- def mapped_participants_count(self):
- """Count of participants with locations."""
- return self.context.mapped_participants_count
-
- @cachedproperty
- def has_mapped_participants(self):
- """Does the team have any mapped participants?"""
- return self.mapped_participants_count > 0
-
- @cachedproperty
- def unmapped_participants(self):
- """Participants (ordered by name) with no recorded locations."""
- return list(self.context.unmapped_participants)
-
- @cachedproperty
- def unmapped_participants_count(self):
- """Count of participants with no recorded locations."""
- return self.context.unmapped_participants_count
-
- @cachedproperty
- def times(self):
- """The current times in time zones with members."""
- zones = set(participant.time_zone
- for participant in self.mapped_participants)
- times = [datetime.now(pytz.timezone(zone))
- for zone in zones]
- timeformat = '%H:%M'
- return sorted(
- set(time.strftime(timeformat) for time in times))
-
- @cachedproperty
- def bounds(self):
- """A dictionary with the bounds and center of the map, or None"""
- if self.has_mapped_participants:
- return self.context.getMappedParticipantsBounds(self.limit)
- return None
-
- @property
- def map_html(self):
- """HTML which shows the map with location of the team's members."""
- return """
- <script type="text/javascript">
- LPJS.use('node', 'lp.app.mapping', function(Y) {
- function renderMap() {
- Y.lp.app.mapping.renderTeamMap(
- %(min_lat)s, %(max_lat)s, %(min_lng)s,
- %(max_lng)s, %(center_lat)s, %(center_lng)s);
- }
- Y.on("domready", renderMap);
- });
- </script>""" % self.bounds
-
- @property
- def map_portlet_html(self):
- """The HTML which shows a small version of the team's map."""
- return """
- <script type="text/javascript">
- LPJS.use('node', 'lp.app.mapping', function(Y) {
- function renderMap() {
- Y.lp.app.mapping.renderTeamMapSmall(
- %(center_lat)s, %(center_lng)s);
- }
- Y.on("domready", renderMap);
- });
- </script>""" % self.bounds
-
-
-class TeamMapData(TeamMapView):
- """An XML dump of the locations of all team members."""
-
- def render(self):
- self.request.response.setHeader(
- 'content-type', 'application/xml;charset=utf-8')
- body = LaunchpadView.render(self)
- return body.encode('utf-8')
-
-
-class TeamMapLtdMixin:
- """A mixin for team views with limited participants."""
- limit = 24
-
-
-class TeamMapLtdView(TeamMapLtdMixin, TeamMapView):
- """Team map view with limited participants."""
-
-
-class TeamMapLtdData(TeamMapLtdMixin, TeamMapData):
- """An XML dump of the locations of limited number of team members."""
-
-
class TeamNavigation(PersonNavigation):
usedfor = ITeam
@@ -1603,11 +1493,6 @@
text = 'Approve or decline members'
return Link(target, text, icon='add')
- def map(self):
- target = '+map'
- text = 'View map and time zones'
- return Link(target, text, icon='meeting')
-
def add_my_teams(self):
target = '+add-my-teams'
text = 'Add one of my teams'
@@ -1723,7 +1608,6 @@
'configure_mailing_list',
'moderate_mailing_list',
'editlanguages',
- 'map',
'polls',
'add_poll',
'join',
=== modified file 'lib/lp/registry/browser/tests/team-views.txt'
--- lib/lp/registry/browser/tests/team-views.txt 2012-02-10 19:38:27 +0000
+++ lib/lp/registry/browser/tests/team-views.txt 2012-02-16 23:29:53 +0000
@@ -67,135 +67,6 @@
form fields.
-+map-portlet
-------------
-
-The team profile page contain the location portlet that shows a map.
-
- >>> team_view = create_initialized_view(ubuntu_team, '+index')
- >>> team_view.has_visible_location
- False
-
-After a member has set his location, the map will be rendered.
-
- >>> login_person(sample_person)
- >>> sample_person.setLocation(
- ... 38.81, 77.1, 'America/New_York', sample_person)
-
- >>> team_view = create_initialized_view(ubuntu_team, '+index')
- >>> team_view.has_visible_location
- True
-
-The small_maps key in the launchpad_views cookie can be set by the viewing
-user to 'false' to indicate that small maps are not wanted.
-
- >>> cookie = 'launchpad_views=small_maps=false'
- >>> team_view = create_initialized_view(
- ... ubuntu_team, '+index', cookie=cookie)
-
-+map
-----
-
-A team's +map page will show a map with markers on the location of all
-members of that team. That page also lists the local times of members.
-
- >>> login('foo.bar@xxxxxxxxxxxxx')
- >>> context = factory.makeTeam(salgado)
- >>> context.mapped_participants_count
- 0
- >>> view = create_initialized_view(context, '+map')
- >>> view.times
- []
-
-
-Once a member is mapped, the map will be rendered. The view provides a cached
-property to access the mapped participants. The views number of times is
-incremented for each timezone the members reside in.
-
- >>> london_member = factory.makePerson(
- ... latitude=51.49, longitude=-0.13, time_zone='Europe/London')
- >>> ignored = context.addMember(london_member, mark)
- >>> context.mapped_participants_count
- 1
- >>> view = create_initialized_view(context, '+map')
- >>> len(view.mapped_participants)
- 1
- >>> len(view.times)
- 1
-
- >>> brazil_member = factory.makePerson(
- ... latitude=-23.60, longitude=-46.64, time_zone='America/Sao_Paulo')
- >>> ignored = context.addMember(brazil_member, mark)
- >>> context.mapped_participants_count
- 2
- >>> view = create_initialized_view(context, '+map')
- >>> len(view.mapped_participants)
- 2
- >>> len(view.times)
- 2
-
-If we had two members in London, though, we wouldn't list London's time
-twice, for obvious reasons.
-
- >>> london_member2 = factory.makePerson(
- ... latitude=51.49, longitude=-0.13, time_zone='Europe/London')
- >>> ignored = context.addMember(london_member2, mark)
- >>> context.mapped_participants_count
- 3
- >>> view = create_initialized_view(context, '+map')
- >>> len(view.times)
- 2
-
-The number of persons returned by mapped_participants is governed by the
-view's limit attribute. The default value is None, meaning there is no
-limit. This view is used for displaying the full map.
-
- >>> print view.limit
- None
- >>> len(view.mapped_participants)
- 3
-
-A portlet for the map also exists which is used for displaying the map
-inside the person or team page. It has the number of participants limited.
-
- >>> view = create_initialized_view(context, '+portlet-map')
- >>> view.limit
- 24
-
- # Hack the limit to demonstrate that it controls mapped_participants.
- >>> view.limit = 1
- >>> len(view.mapped_participants)
- 1
-
-The view provides the count of mapped and unmapped members. The counts
-are cached to improve performance.
-
- >>> view = create_initialized_view(context, '+map')
- >>> view.mapped_participants_count
- 3
- >>> view.unmapped_participants_count
- 1
-
-The template can ask if the team has mapped members, so that it does not
-need to work with counts or the list of members.
-
- >>> view.has_mapped_participants
- True
-
-The cached bounds of the mapped members of the team can be used to define
-the scale of a map.
-
- >>> bounds = view.bounds
- >>> for key in sorted(bounds):
- ... print "%s: %s" % (key, bounds[key])
- center_lat: 13...
- center_lng: -23...
- max_lat: 51...
- max_lng: -0...
- min_lat: -23...
- min_lng: -46...
-
-
Contacting the team
-------------------
=== modified file 'lib/lp/registry/doc/personlocation.txt'
--- lib/lp/registry/doc/personlocation.txt 2011-12-24 17:49:30 +0000
+++ lib/lp/registry/doc/personlocation.txt 2012-02-16 23:29:53 +0000
@@ -1,219 +1,41 @@
Locations for People and Teams
==============================
-The PersonLocation object stores information about the location and time
-zone of a person. It also remembers who provided that information, and
-when. This is designed to make it possible to have people provide
-location / time zone info for other people in a wiki style.
+We no longer allow people to set their geographical locations, but their time
+zone (which they can set) is stored together with their latitude/longitude (in
+PersonLocation). Until we move the time zone back to the Person table
+(bug=933699), we'll maintain the setLocation() API on IPerson.
>>> from lp.services.webapp.testing import verifyObject
- >>> from lp.registry.interfaces.location import IObjectWithLocation
>>> from lp.registry.interfaces.person import IPersonSet
>>> personset = getUtility(IPersonSet)
-A Person implements the IObjectWithLocation interface.
-
- >>> login('test@xxxxxxxxxxxxx')
>>> marilize = personset.getByName('marilize')
- >>> verifyObject(IObjectWithLocation, marilize)
- True
-
-A Person has a PersonLocation record, if there is any location
-information associated with them. That implements the IPersonLocation
-interface.
-
- >>> from lp.registry.interfaces.location import IPersonLocation
- >>> marilize.location
- <PersonLocation...
- >>> verifyObject(IPersonLocation, marilize.location)
- True
-
-In some cases, a person has a time zone, but no location.
-
>>> print marilize.time_zone
Africa/Maseru
>>> print marilize.latitude
None
-
-The location for a person is set with the "setLocation" method. This
-requires that the user providing the information is passed as a
-parameter.
-
-A user cannot set another user's location.
-
- >>> jdub = personset.getByName('jdub')
- >>> login_person(jdub)
+ >>> print marilize.longitude
+ None
+
+setLocation() will always set the time zone to the given value and both
+latitude and longitude to None, regardless of what was passed in.
+
>>> cprov = personset.getByName('cprov')
- >>> cprov.setLocation(-43.0, -62.1, 'America/Sao_Paulo', jdub)
- Traceback (most recent call last):
- ...
- Unauthorized:...
-
-A user can set his own location.
-
>>> login_person(cprov)
>>> cprov.setLocation(-43.2, -61.93, 'America/Sao_Paulo', cprov)
-
-cprov can change his location information. We need to deal
-with some floating point precision issues here, hence the rounding.
-
- >>> login_person(cprov)
- >>> cprov.setLocation(-43.52, -61.93, 'America/Sao_Paulo', cprov)
- >>> abs(cprov.latitude + 43.52) < 0.001
- True
-
-Admins can set a user's location.
-
- >>> admin = personset.getByName('name16')
- >>> login_person(admin)
- >>> cprov.setLocation(-43.0, -62.1, 'America/Sao_Paulo', admin)
- >>> abs(cprov.longitude + 62.1) < 0.001
- True
+ >>> print cprov.time_zone
+ America/Sao_Paulo
+ >>> print cprov.latitude
+ None
+ >>> print cprov.longitude
+ None
We cannot store a location for a team, though.
+ >>> jdub = personset.getByName('jdub')
>>> guadamen = personset.getByName('guadamen')
>>> guadamen.setLocation(34.5, 23.1, 'Africa/Maseru', jdub)
Traceback (most recent call last):
...
AssertionError:...
-
-Nor can we set only the latitude of a person.
-
- >>> cprov.setLocation(-43.0, None, 'America/Sao_Paulo', admin)
- Traceback (most recent call last):
- ...
- AssertionError:...
-
-Similarly, we can't set only the longitude.
-
- >>> cprov.setLocation(None, -43.0, 'America/Sao_Paulo', admin)
- Traceback (most recent call last):
- ...
- AssertionError:...
-
-We can get lists of the participants in a team that do, or do not, have
-locations. Specifically, we mean latitude/longitude data, not time zone
-data.
-
-When we get mapped participants, and unmapped participants, we only mean
-the individuals, not other teams. We'll show that guadamen has a
-sub-team, ubuntu-team, and that it still does not appear in either
-mapped_participants or unmapped_participants (although its members do).
-
- >>> for member in guadamen.activemembers:
- ... if member.teamowner is not None:
- ... print member.name
- ubuntu-team
- >>> len(guadamen.getMappedParticipants())
- 2
- >>> for mapped in guadamen.getMappedParticipants():
- ... if mapped.teamowner is not None:
- ... print mapped.name
- >>> guadamen.unmapped_participants.count()
- 7
- >>> for unmapped in guadamen.unmapped_participants:
- ... if unmapped.teamowner is not None:
- ... print unmapped.name
-
-When we iterate over the mapped_participants in a team, their locations
-have been pre-cached so that we don't hit the database everytime we
-access a person's .location property.
-
- >>> from lp.services.propertycache import get_property_cache
- >>> for mapped in guadamen.getMappedParticipants():
- ... cache = get_property_cache(mapped)
- ... if ("location" not in cache or
- ... not verifyObject(IPersonLocation, cache.location)):
- ... print 'No cached location on %s' % mapped.name
-
-The mapped_participants methods takes a optional argument to limit the
-number of persons in the returned list.
-
- >>> mapped = guadamen.getMappedParticipants(limit=1)
- >>> len(mapped)
- 1
-
-The count of mapped and unmapped members can also be retrieved, which is
-faster than getting the resultset of members.
-
- >>> guadamen.mapped_participants_count
- 2
- >>> guadamen.unmapped_participants_count
- 7
-
-The bounds of the mapped members can be retrieved. It is a dict that contains
-the minimum maximum, and central longitudes and latitudes.
-
- >>> bounds = guadamen.getMappedParticipantsBounds()
- >>> for key in sorted(bounds):
- ... print "%s: %s" % (key, bounds[key])
- center_lat: 4...
- center_lng: -30...
- max_lat: 52...
- max_lng: 0...
- min_lat: -43...
- min_lng: -62...
-
-Calling getMappedParticipantsBounds() on a team without members is an error.
-
- >>> unmapped_team = factory.makeTeam()
- >>> unmapped_team.getMappedParticipantsBounds()
- Traceback (most recent call last):
- ...
- AssertionError: This method cannot be called when
- mapped_participants_count == 0.
-
-
-Location visibility
--------------------
-
-Some people may not want their location to be disclosed to others, so
-we provide a way for them to hide their location from other users. By
-default a person's location is visible.
-
- >>> salgado = personset.getByName('salgado')
- >>> login_person(salgado)
- >>> salgado.setLocation(-43.0, -62.1, 'America/Sao_Paulo', salgado)
- >>> salgado.location.visible
- True
- >>> salgado.location.latitude
- -43...
-
- >>> login_person(jdub)
- >>> salgado.location.latitude
- -43...
-
-But it can be changed through the setLocationVisibility() method. If the
-visibility is set to False, only the person himself will be able to see
-the location data except for time zone.
-
- >>> login_person(salgado)
- >>> salgado.setLocationVisibility(False)
- >>> salgado.location.visible
- False
-
- >>> login_person(jdub)
- >>> print salgado.time_zone
- America/Sao_Paulo
- >>> salgado.latitude
- Traceback (most recent call last):
- ...
- Unauthorized:...
- >>> salgado.longitude
- Traceback (most recent call last):
- ...
- Unauthorized:...
- >>> salgado.location.latitude
- Traceback (most recent call last):
- ...
- Unauthorized:...
-
-A team's .mapped_participants will also exclude the members who made
-their location invisible.
-
- >>> admins = personset.getByName('admins')
- >>> salgado in admins.activemembers
- True
- >>> salgado in admins.getMappedParticipants()
- False
=== modified file 'lib/lp/registry/interfaces/location.py'
--- lib/lp/registry/interfaces/location.py 2011-12-24 16:54:44 +0000
+++ lib/lp/registry/interfaces/location.py 2012-02-16 23:29:53 +0000
@@ -47,12 +47,18 @@
class IHasLocation(Interface):
"""An interface supported by objects with a defined location."""
- latitude = exported(doNotSnapshot(
- Float(title=_("The latitude of this object."),
- required=False, readonly=True)))
- longitude = exported(doNotSnapshot(
- Float(title=_("The longitude of this object."),
- required=False, readonly=True)))
+ latitude = exported(
+ doNotSnapshot(
+ Float(title=_("The latitude of this object."),
+ required=False, readonly=True)),
+ ('devel', dict(exported=False)),
+ exported=True)
+ longitude = exported(
+ doNotSnapshot(
+ Float(title=_("The longitude of this object."),
+ required=False, readonly=True)),
+ ('devel', dict(exported=False)),
+ exported=True)
time_zone = exported(doNotSnapshot(
Choice(title=_('The time zone of this object.'),
required=False, readonly=True,
=== modified file 'lib/lp/registry/interfaces/person.py'
--- lib/lp/registry/interfaces/person.py 2012-02-16 08:27:37 +0000
+++ lib/lp/registry/interfaces/person.py 2012-02-16 23:29:53 +0000
@@ -130,7 +130,6 @@
from lp.registry.interfaces.jabber import IJabberID
from lp.registry.interfaces.location import (
IHasLocation,
- ILocationRecord,
IObjectWithLocation,
ISetLocation,
)
@@ -719,7 +718,7 @@
@operation_for_version("beta")
def transitionVisibility(visibility, user):
"""Set visibility of IPerson.
-
+
:param visibility: The PersonVisibility to change to.
:param user: The user requesting the change.
:raises: `ImmutableVisibilityError` when the visibility can not
@@ -1611,31 +1610,6 @@
exported_as='proposed_members')
proposed_member_count = Attribute("Number of PROPOSED members")
- mapped_participants_count = Attribute(
- "The number of mapped participants")
- unmapped_participants = doNotSnapshot(
- CollectionField(
- title=_("List of participants with no coordinates recorded."),
- value_type=Reference(schema=Interface)))
- unmapped_participants_count = Attribute(
- "The number of unmapped participants")
-
- def getMappedParticipants(limit=None):
- """List of participants with coordinates.
-
- :param limit: The optional maximum number of items to return.
- :return: A list of `IPerson` objects
- """
-
- def getMappedParticipantsBounds():
- """Return a dict of the bounding longitudes latitudes, and centers.
-
- This method cannot be called if there are no mapped participants.
-
- :return: a dict containing: min_lat, min_lng, max_lat, max_lng,
- center_lat, and center_lng
- """
-
def getMembersWithPreferredEmails():
"""Returns a result set of persons with precached addresses.
@@ -1711,13 +1685,6 @@
:param team: The team to leave.
"""
- @operation_parameters(
- visible=copy_field(ILocationRecord['visible'], required=True))
- @export_write_operation()
- @operation_for_version("beta")
- def setLocationVisibility(visible):
- """Specify the visibility of a person's location and time zone."""
-
def setMembershipData(person, status, reviewer, expires=None,
comment=None):
"""Set the attributes of the person's membership on this team.
@@ -2585,7 +2552,6 @@
'invited_members',
'deactivatedmembers',
'expiredmembers',
- 'unmapped_participants',
]:
IPersonViewRestricted[name].value_type.schema = IPerson
=== modified file 'lib/lp/registry/model/person.py'
--- lib/lp/registry/model/person.py 2012-02-16 08:27:37 +0000
+++ lib/lp/registry/model/person.py 2012-02-16 23:29:53 +0000
@@ -705,6 +705,24 @@
Or(OAuthRequestToken.date_expires == None,
OAuthRequestToken.date_expires > UTC_NOW))
+ @property
+ def latitude(self):
+ """See `IHasLocation`.
+
+ We no longer allow users to set their geographical location but we
+ need to keep this because it was exported on version 1.0 of the API.
+ """
+ return None
+
+ @property
+ def longitude(self):
+ """See `IHasLocation`.
+
+ We no longer allow users to set their geographical location but we
+ need to keep this because it was exported on version 1.0 of the API.
+ """
+ return None
+
@cachedproperty
def location(self):
"""See `IObjectWithLocation`."""
@@ -719,33 +737,6 @@
# enough rights to see it.
return ProxyFactory(self.location).time_zone
- @property
- def latitude(self):
- """See `IHasLocation`."""
- if self.location is None:
- return None
- # Wrap the location with a security proxy to make sure the user has
- # enough rights to see it.
- return ProxyFactory(self.location).latitude
-
- @property
- def longitude(self):
- """See `IHasLocation`."""
- if self.location is None:
- return None
- # Wrap the location with a security proxy to make sure the user has
- # enough rights to see it.
- return ProxyFactory(self.location).longitude
-
- def setLocationVisibility(self, visible):
- """See `ISetLocation`."""
- assert not self.is_team, 'Cannot edit team location.'
- if self.location is None:
- get_property_cache(self).location = PersonLocation(
- person=self, visible=visible)
- else:
- self.location.visible = visible
-
def setLocation(self, latitude, longitude, time_zone, user):
"""See `ISetLocation`."""
assert not self.is_team, 'Cannot edit team location.'
@@ -2062,92 +2053,6 @@
)
return (self.subscriptionpolicy in open_types)
- def _getMappedParticipantsLocations(self, limit=None):
- """See `IPersonViewRestricted`."""
- return PersonLocation.select("""
- PersonLocation.person = TeamParticipation.person AND
- TeamParticipation.team = %s AND
- -- We only need to check for a latitude here because there's a DB
- -- constraint which ensures they are both set or unset.
- PersonLocation.latitude IS NOT NULL AND
- PersonLocation.visible IS TRUE AND
- Person.id = PersonLocation.person AND
- Person.teamowner IS NULL
- """ % sqlvalues(self.id),
- clauseTables=['TeamParticipation', 'Person'],
- prejoins=['person', ], limit=limit)
-
- def getMappedParticipants(self, limit=None):
- """See `IPersonViewRestricted`."""
- # Pre-cache this location against its person. Since we'll always
- # iterate over all persons returned by this property (to build the map
- # of team members), it becomes more important to cache their locations
- # than to return a lazy SelectResults (or similar) object that only
- # fetches the rows when they're needed.
- locations = self._getMappedParticipantsLocations(limit=limit)
- for location in locations:
- get_property_cache(location.person).location = location
- participants = set(location.person for location in locations)
- # Cache the ValidPersonCache query for all mapped participants.
- if len(participants) > 0:
- sql = "id IN (%s)" % ",".join(sqlvalues(*participants))
- list(ValidPersonCache.select(sql))
- getUtility(IPersonSet).cacheBrandingForPeople(participants)
- return list(participants)
-
- @property
- def mapped_participants_count(self):
- """See `IPersonViewRestricted`."""
- return self._getMappedParticipantsLocations().count()
-
- def getMappedParticipantsBounds(self, limit=None):
- """See `IPersonViewRestricted`."""
- max_lat = -90.0
- min_lat = 90.0
- max_lng = -180.0
- min_lng = 180.0
- locations = self._getMappedParticipantsLocations(limit)
- if self.mapped_participants_count == 0:
- raise AssertionError(
- 'This method cannot be called when '
- 'mapped_participants_count == 0.')
- latitudes = sorted(location.latitude for location in locations)
- if latitudes[-1] > max_lat:
- max_lat = latitudes[-1]
- if latitudes[0] < min_lat:
- min_lat = latitudes[0]
- longitudes = sorted(location.longitude for location in locations)
- if longitudes[-1] > max_lng:
- max_lng = longitudes[-1]
- if longitudes[0] < min_lng:
- min_lng = longitudes[0]
- center_lat = (max_lat + min_lat) / 2.0
- center_lng = (max_lng + min_lng) / 2.0
- return dict(
- min_lat=min_lat, min_lng=min_lng, max_lat=max_lat,
- max_lng=max_lng, center_lat=center_lat, center_lng=center_lng)
-
- @property
- def unmapped_participants(self):
- """See `IPersonViewRestricted`."""
- return Person.select("""
- Person.id = TeamParticipation.person AND
- TeamParticipation.team = %s AND
- TeamParticipation.person NOT IN (
- SELECT PersonLocation.person
- FROM PersonLocation INNER JOIN TeamParticipation ON
- PersonLocation.person = TeamParticipation.person
- WHERE TeamParticipation.team = %s AND
- PersonLocation.latitude IS NOT NULL) AND
- Person.teamowner IS NULL
- """ % sqlvalues(self.id, self.id),
- clauseTables=['TeamParticipation'])
-
- @property
- def unmapped_participants_count(self):
- """See `IPersonViewRestricted`."""
- return self.unmapped_participants.count()
-
@property
def open_membership_invitations(self):
"""See `IPerson`."""
=== modified file 'lib/lp/registry/stories/location/personlocation-edit.txt'
--- lib/lp/registry/stories/location/personlocation-edit.txt 2012-01-15 13:32:27 +0000
+++ lib/lp/registry/stories/location/personlocation-edit.txt 2012-02-16 23:29:53 +0000
@@ -1,16 +1,15 @@
-Edit person location information
-================================
+Edit person time zone information
+=================================
-A person's location is only editable by people who have launchpad.Edit on
+A person's time zone is only editable by people who have launchpad.Edit on
the person, which is that person and admins.
>>> login('test@xxxxxxxxxxxxx')
>>> zzz = factory.makePerson(
- ... name='zzz', time_zone='Africa/Maseru', email='zzz@xxxxxxx',
- ... latitude=None, longitude=None)
+ ... name='zzz', time_zone='Africa/Maseru', email='zzz@xxxxxxx')
>>> logout()
-A user cannot set another user's location.
+A user cannot set another user's +editlocation page.
>>> nopriv_browser = setupBrowser(auth="Basic no-priv@xxxxxxxxxxxxx:test")
>>> nopriv_browser.open('http://launchpad.dev/~zzz/+editlocation')
@@ -18,35 +17,15 @@
...
Unauthorized:...
-A user can set his own location:
+A user can set his own time zone:
>>> self_browser = setupBrowser(auth="Basic zzz@xxxxxxx:test")
>>> self_browser.open('http://launchpad.dev/~zzz/+editlocation')
- >>> self_browser.getControl(name='field.location.latitude').value = (
- ... '39.48')
- >>> self_browser.getControl(name='field.location.longitude').value = (
- ... '-0.40')
- >>> self_browser.getControl(name='field.location.time_zone').value = [
+ >>> self_browser.getControl(name='field.time_zone').value = [
... 'Europe/Madrid']
>>> self_browser.getControl('Update').click()
>>> login('zzz@xxxxxxx')
- >>> zzz.latitude
- 39.4...
- >>> zzz.longitude
- -0.4...
>>> zzz.time_zone
u'Europe/Madrid'
>>> logout()
-
-The user can change his location:
-
- >>> self_browser.open('http://launchpad.dev/~zzz/+editlocation')
- >>> self_browser.getControl(name='field.location.latitude').value
- '39.48'
-
-And so can a Launchpad admin:
-
- >>> admin_browser.open('http://launchpad.dev/~zzz/+editlocation')
- >>> admin_browser.getControl(name='field.location.latitude').value
- '39.48'
=== removed file 'lib/lp/registry/stories/location/personlocation.txt'
--- lib/lp/registry/stories/location/personlocation.txt 2010-09-21 03:30:43 +0000
+++ lib/lp/registry/stories/location/personlocation.txt 1970-01-01 00:00:00 +0000
@@ -1,18 +0,0 @@
-Person Locations
-================
-
-People can have a location and time zone in Launchpad. In some cases, a
-person has a time zone, but no location. We test that their home page renders
-without problems.
-
- >>> login('test@xxxxxxxxxxxxx')
- >>> zzz = factory.makePerson(name='zzz', time_zone='Africa/Maseru',
- ... latitude=None, longitude=None)
- >>> logout()
-
-Any person can see another person's time zone if their is also
-location data set, otherwise the location portlet does not show up.
-
- >>> anon_browser.open('http://launchpad.dev/~zzz')
- >>> print extract_text(
- ... find_tag_by_id(anon_browser.contents, 'portlet-map'))
=== removed file 'lib/lp/registry/stories/location/team-map.txt'
--- lib/lp/registry/stories/location/team-map.txt 2011-12-28 17:03:06 +0000
+++ lib/lp/registry/stories/location/team-map.txt 1970-01-01 00:00:00 +0000
@@ -1,86 +0,0 @@
-The map of a team's members
-===========================
-
-The map depends on a stream of XML-formatted data, giving the locations of
-all members of the team. We show that this stream works for teams with, and
-without, mapped members.
-
- >>> anon_browser.open('http://launchpad.dev/~guadamen/+mapdata')
- >>> print anon_browser.contents
- <?xml version="1.0"...
- <participants>
- <!--
- This collection of team location data is (c) Canonical Ltd and
- contributors, and the format may be changed at any time.
- -->
- <participant
- displayname="Colin Watson"
- name="kamion"
- logo_html="<img alt=""
- width="64" height="64"
- src="/@@/person-logo" />"
- url="/~kamion"
- local_time="..."
- lat="52.2"
- lng="0.3"/>
- </participants>
- <BLANKLINE>
-
- >>> anon_browser.open('http://launchpad.dev/~name21/+mapdata')
- >>> print anon_browser.contents
- <?xml version="1.0"...
- <participants>
- <!--
- This collection of team location data is (c) Canonical Ltd and
- contributors, and the format may be changed at any time.
- -->
- </participants>
- <BLANKLINE>
-
-The team index page has a map with data limited to 24 members and uses
-a slightly different URL. We can see the URL works though there isn't
-enough test data to demonstrate the limit but that is done in the view
-test.
-
- >>> anon_browser.open('http://launchpad.dev/~guadamen/+mapdataltd')
- >>> print anon_browser.contents
- <?xml version="1.0"...
- <participants>
- <!--
- This collection of team location data is (c) Canonical Ltd and
- contributors, and the format may be changed at any time.
- -->
- <participant
- displayname="Colin Watson"
- name="kamion"
- logo_html="<img alt=""
- width="64" height="64"
- src="/@@/person-logo" />"
- url="/~kamion"
- local_time="..."
- lat="52.2"
- lng="0.3"/>
- </participants>
- <BLANKLINE>
-
-
-+mapdata
---------
-
-The display name of all team participants will be escaped to prevent
-XSS attacks on any callsite of +mapdata.
-
- >>> from lp.testing.layers import MemcachedLayer
-
- >>> admin_browser.open('http://launchpad.dev/~kamion/+edit')
- >>> admin_browser.getControl('Display Name').value = (
- ... "<script>alert('Colin \"nasty\"');</script>")
- >>> admin_browser.getControl('Save Changes').click()
-
- >>> MemcachedLayer.purge()
- >>> anon_browser.open('http://launchpad.dev/~guadamen/+mapdata')
- >>> print anon_browser.contents
- <?xml version="1.0"...
- ...displayname="&lt;script&gt;alert('Colin
- &quot;nasty&quot;');&lt;/script&gt;"
- ...
=== modified file 'lib/lp/registry/stories/person/xx-person-home.txt'
--- lib/lp/registry/stories/person/xx-person-home.txt 2011-12-18 13:45:20 +0000
+++ lib/lp/registry/stories/person/xx-person-home.txt 2012-02-16 23:29:53 +0000
@@ -141,12 +141,16 @@
Summary Pagelets
----------------
-A person's homepage also lists Karma information:
+A person's homepage also lists Karma and Time zone information:
>>> browser.open('http://launchpad.dev/~mark')
>>> print extract_text(find_tag_by_id(browser.contents, 'karma'))
Karma: 130 Karma help
+ >>> browser.open('http://launchpad.dev/~kamion')
+ >>> print extract_text(find_tag_by_id(browser.contents, 'timezone'))
+ Time zone: Europe/London (UTC+0000)
+
Negative Ubuntu Code of Conduct signatory status is only displayed for
yourself; others won't see it:
@@ -173,25 +177,6 @@
2005-06-06
-Time zones
-..........
-
-The user's time zone is displayed next to their location details:
-
- >>> browser.open('http://launchpad.dev/~kamion')
- >>> print extract_text(
- ... find_tag_by_id(browser.contents, 'portlet-map'))
- Location
- Time zone: Europe/London...
-
-If the user does not have location data set then the portlet will not be
-shown.
-
- >>> browser.open('http://launchpad.dev/~bac')
- >>> print extract_text(
- ... find_tag_by_id(browser.contents, 'portlet-map'))
-
-
Table of contributions
----------------------
=== modified file 'lib/lp/registry/stories/webservice/xx-personlocation.txt'
--- lib/lp/registry/stories/webservice/xx-personlocation.txt 2009-10-01 12:30:32 +0000
+++ lib/lp/registry/stories/webservice/xx-personlocation.txt 2012-02-16 23:29:53 +0000
@@ -1,7 +1,9 @@
= Person location =
The location of a person is readable through the Web Service API, and can
-be set that way, too.
+be set that way too, but it has been deprecated as we no longer have that
+information in our database, so the latitude/longitude will always be None.
+The time_zone attribute has not been deprecated, though.
We start with the case where there is no information about the user's
location, at all.
@@ -14,30 +16,8 @@
>>> print jdub['longitude']
None
-We show that we handle the case where there is no latitude or
-longitude information.
-
- >>> marilize = webservice.get("/~marilize").jsonBody()
- >>> print marilize['time_zone']
- Africa/Maseru
- >>> print marilize['latitude']
- None
- >>> print marilize['longitude']
- None
-
-And here is an example, where we have location and time zone information for
-a user:
-
- >>> kamion = webservice.get("/~kamion").jsonBody()
- >>> print kamion['time_zone']
- Europe/London
- >>> print kamion['latitude']
- 52.2
- >>> print kamion['longitude']
- 0.3
-
-It is also possible to set the location, if it is not yet locked by being
-provided by the user themselves.
+It is also possible to set the location, but as you can see the
+latitude/longitude read via the Web API will still be None.
>>> print webservice.get("/~jdub").jsonBody()['time_zone']
None
@@ -48,3 +28,7 @@
...
>>> webservice.get("/~jdub").jsonBody()['time_zone']
u'Australia/Sydney'
+ >>> print jdub['latitude']
+ None
+ >>> print jdub['longitude']
+ None
=== modified file 'lib/lp/registry/templates/person-index.pt'
--- lib/lp/registry/templates/person-index.pt 2011-06-30 12:58:22 +0000
+++ lib/lp/registry/templates/person-index.pt 2012-02-16 23:29:53 +0000
@@ -96,8 +96,6 @@
</div>
</div>
-
- <div tal:content="structure context/@@+portlet-map" />
</tal:is-valid-person>
<div id="not-lp-user-or-team"
=== modified file 'lib/lp/registry/templates/person-portlet-contact-details.pt'
--- lib/lp/registry/templates/person-portlet-contact-details.pt 2011-11-26 04:03:29 +0000
+++ lib/lp/registry/templates/person-portlet-contact-details.pt 2012-02-16 23:29:53 +0000
@@ -169,7 +169,7 @@
</dd>
</dl>
- <dl tal:condition="context/time_zone">
+ <dl id="timezone" tal:condition="context/time_zone">
<dt>Time zone:
<a tal:replace="structure overview_menu/editlocation/fmt:icon" />
</dt>
=== removed file 'lib/lp/registry/templates/person-portlet-map.pt'
--- lib/lp/registry/templates/person-portlet-map.pt 2010-09-21 03:30:43 +0000
+++ lib/lp/registry/templates/person-portlet-map.pt 1970-01-01 00:00:00 +0000
@@ -1,21 +0,0 @@
-<tal:root
- xmlns:tal="http://xml.zope.org/namespaces/tal"
- xmlns:metal="http://xml.zope.org/namespaces/metal"
- xmlns:i18n="http://xml.zope.org/namespaces/i18n"
- omit-tag="">
-
-<div class="portlet" id="portlet-map"
- tal:define="overview_menu context/menu:overview">
-
- <tal:show-map condition="view/should_show_map_portlet">
- <h2>Location</h2>
-
- <div tal:condition="context/time_zone">
- <strong>Time zone:</strong>
- <span tal:replace="context/time_zone">UTC</span>
- <a tal:replace="structure overview_menu/editlocation/fmt:icon" />
- </div>
- </tal:show-map>
-
-</div>
-</tal:root>
=== removed file 'lib/lp/registry/templates/team-map-data.pt'
--- lib/lp/registry/templates/team-map-data.pt 2010-06-24 20:22:57 +0000
+++ lib/lp/registry/templates/team-map-data.pt 1970-01-01 00:00:00 +0000
@@ -1,17 +0,0 @@
-<?xml version="1.0"?><!--*- mode: nxml -*-->
-<participants xmlns:tal="http://xml.zope.org/namespaces/tal"
- tal:content="cache:public, 30 minute">
- <!--
- This collection of team location data is (c) Canonical Ltd and
- contributors, and the format may be changed at any time.
- -->
- <participant tal:repeat="participant view/mapped_participants"
- tal:attributes="
- lat participant/latitude;
- lng participant/longitude;
- displayname participant/fmt:displayname/fmt:escape;
- local_time participant/fmt:local-time;
- logo_html participant/image:logo;
- url participant/fmt:url;
- name participant/name" />
-</participants>
=== removed file 'lib/lp/registry/templates/team-map.pt'
--- lib/lp/registry/templates/team-map.pt 2010-05-17 17:29:08 +0000
+++ lib/lp/registry/templates/team-map.pt 1970-01-01 00:00:00 +0000
@@ -1,45 +0,0 @@
-<html
- xmlns="http://www.w3.org/1999/xhtml"
- xmlns:tal="http://xml.zope.org/namespaces/tal"
- xmlns:metal="http://xml.zope.org/namespaces/metal"
- xmlns:i18n="http://xml.zope.org/namespaces/i18n"
- metal:use-macro="view/macro:page/main_only"
- i18n:domain="launchpad"
->
-
-<body>
-
-<div metal:fill-slot="main">
-
- <div style="height:420px;">
- <div tal:condition="not: view/has_mapped_participants">
- None of the current participants in
- <tal:teamname replace="context/fmt:displayname" /> have a recorded
- location.
- </div>
-
- <div
- tal:content="cache:public, 1 hour">
- <tal:some_mapped condition="view/has_mapped_participants">
- <div id="team_map_div"
- style="width: 75%; height: 250px; border: 1px; float: left;
- margin: 0 14px 0 0;">
- </div>
- <tal:map replace="structure view/map_html" />
- </tal:some_mapped>
-
- <div tal:condition="view/has_mapped_participants" style="float:left;">
- <div style="margin-bottom: 4px;">
- <strong>Members' current local time:</strong>
- </div>
- <tal:time_set repeat="time view/times">
- <tal:times replace="time" /><br />
- </tal:time_set>
- </div>
- </div>
- </div>
-
-</div>
-
-</body>
-</html>
=== removed file 'lib/lp/registry/templates/team-portlet-map.pt'
--- lib/lp/registry/templates/team-portlet-map.pt 2010-09-21 04:21:16 +0000
+++ lib/lp/registry/templates/team-portlet-map.pt 1970-01-01 00:00:00 +0000
@@ -1,37 +0,0 @@
-<tal:root
- xmlns:tal="http://xml.zope.org/namespaces/tal"
- xmlns:metal="http://xml.zope.org/namespaces/metal"
- xmlns:i18n="http://xml.zope.org/namespaces/i18n"
- omit-tag="">
-
-<div class="portlet" id="portlet-map" style="margin-bottom: 0px;"
- tal:define="link context/menu:overview/map">
-<table>
-<tr><td>
- <h2>
- <span class="see-all">
- <a tal:attributes="href link/fmt:url">View map and time zones</a>
- </span>
- Location
- </h2>
-
- <tal:can_view condition="context/required:launchpad.View">
- <div style="width: 400px;" tal:condition="view/has_mapped_participants">
- <div id="team_map_actions"
- style="position:relative; z-index: 9999;
- float:right; width: 8.5em; margin: 2px;
- background-color: white; padding-bottom:1px;"></div>
- <div id="team_map_div" class="small-map"
- style="height: 200px; border: 1px; margin-top: 4px;"></div>
- <tal:mapscript replace="structure view/map_portlet_html" />
- </div>
-
- <tal:no-map tal:condition="not: view/has_mapped_participants">
- <a tal:attributes="href link/target"
- ><img src="/+icing/portlet-map-unknown.png" /></a>
- </tal:no-map>
- </tal:can_view>
-</td></tr>
-</table>
-</div>
-</tal:root>
=== modified file 'lib/lp/registry/tests/test_person.py'
--- lib/lp/registry/tests/test_person.py 2012-02-16 08:27:37 +0000
+++ lib/lp/registry/tests/test_person.py 2012-02-16 23:29:53 +0000
@@ -718,8 +718,7 @@
'all_members_prepopulated', 'approvedmembers',
'deactivatedmembers', 'expiredmembers', 'inactivemembers',
'invited_members', 'member_memberships', 'pendingmembers',
- 'proposedmembers', 'unmapped_participants', 'longitude',
- 'latitude', 'time_zone',
+ 'proposedmembers', 'time_zone',
)
snap = Snapshot(self.myteam, providing=providedBy(self.myteam))
for name in omitted:
=== modified file 'lib/lp/registry/tests/test_personset.py'
--- lib/lp/registry/tests/test_personset.py 2012-02-09 20:23:49 +0000
+++ lib/lp/registry/tests/test_personset.py 2012-02-16 23:29:53 +0000
@@ -32,7 +32,6 @@
from lp.registry.interfaces.person import (
IPersonSet,
PersonCreationRationale,
- PersonVisibility,
TeamEmailAddressError,
)
from lp.registry.interfaces.personnotification import IPersonNotificationSet
@@ -140,7 +139,7 @@
person.is_valid_person
person.karma
person.is_ubuntu_coc_signer
- person.location
+ person.location,
person.archive
person.preferredemail
self.assertThat(recorder, HasQueryCount(LessThan(1)))
=== modified file 'lib/lp/registry/xmlrpc/canonicalsso.py'
--- lib/lp/registry/xmlrpc/canonicalsso.py 2012-02-14 09:28:26 +0000
+++ lib/lp/registry/xmlrpc/canonicalsso.py 2012-02-16 23:29:53 +0000
@@ -38,7 +38,7 @@
if person is None:
return
- time_zone = person.location and person.location.time_zone
+ time_zone = person.time_zone
team_names = dict(
(removeSecurityProxy(t).name, t.private)
for t in person.teams_participated_in)