← Back to team overview

launchpad-reviewers team mailing list archive

[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>
-    ... "&lt;script&gt;alert('John &quot;nasty&quot; O'Brien');&lt;/script&gt;",...

=== 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="&lt;img alt=&quot;&quot;
-            width=&quot;64&quot; height=&quot;64&quot;
-            src=&quot;/@@/person-logo&quot; /&gt;"
-        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="&lt;img alt=&quot;&quot;
-            width=&quot;64&quot; height=&quot;64&quot;
-                src=&quot;/@@/person-logo&quot; /&gt;"
-        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="&amp;lt;script&amp;gt;alert('Colin
-        &amp;quot;nasty&amp;quot;');&amp;lt;/script&amp;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)