← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~sinzui/launchpad/error-pages into lp:launchpad

 

Curtis Hovey has proposed merging lp:~sinzui/launchpad/error-pages into lp:launchpad.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)
Related bugs:
  Bug #432889 in Launchpad itself: "+authorize-token has no heading"
  https://bugs.launchpad.net/launchpad/+bug/432889
  Bug #435832 in Launchpad itself: "Launchpad email verification shows a near-empty page"
  https://bugs.launchpad.net/launchpad/+bug/435832
  Bug #580240 in Launchpad itself: "More Mobile Friendly Login and OAuth Page "
  https://bugs.launchpad.net/launchpad/+bug/580240
  Bug #938889 in Launchpad itself: "text of Unauthorized error page does not explain privacy and sharing"
  https://bugs.launchpad.net/launchpad/+bug/938889

For more details, see:
https://code.launchpad.net/~sinzui/launchpad/error-pages/+merge/94574

Update error pages to look and read like other Lp pages.

    Pre-implementation: lifeless, the purple squad.

This issue came up while fixing some 403 errors on private team pages.
We want 403's to look like other private pages. Well All error pages should
look like Lp pages. We want to reduce the exceptional layout rules.

The launchpad error pages have layout and text problems. The 403 page is
the worst. It should show the privacy banner and explain that the
content of the page is not shared with the user. The instructions about
the users identity make more sense when if the user knows that the
private information may have been shared with another identity.

Layout/text changes to the 403 should be copied to the other error pages
as needed so that the pages look more like other Lp pages and have a
consistent message.

ADDENDUM: Sorry about the large diff. Most of the changes are a mechanical
update from locationless to main_only layouts.

--------------------------------------------------------------------

RULES

    * Update the 403 page to show the privacy banner and revise the
      text to explain sharing.
    * Consider updating other error pages to make them consistent.
    * The layout problem is actually cause by the locationless layout.
      * It does not provide a header so users see a naked page.
        https://bugs.launchpad.net/launchpad/+bug/580240
        It looks like Lp had an error drawing the page.
      * We discussed removing the locationless layout shortly after the
        3.0 design. We do want to show the Lp header, but the context
        must be Lp's root, not the project your error/operation was in.
        * The only pages that that do use Lp's root are error pages because
          an issue came up during traversal. The traverse objects are
          incomplete and still in the request. We do not want to show
          these -- some might be private objects.
        * Ensure errors do not have breadcrumbs.
      * If the breadcrumb and privacy presentation issues are solved,
        the locationless layout can be removed.


QA

There are actually very few views that use the many locationless templates:
    * All errors use th same base view.
    * CoC uses the same base view
    * LoginToken uses th same base view.
    * Oauth has only one view

    Forbidden error
    * Visit https://qastaging.launchpad.net/puzzleit/+edit
    * Verify the page shows the privacy banner.
    * Verify the header is show with the Lp brand.
    * Verify the page states the the information is not shared with the user.
    * Verify that the page states the users identity and suggests logging
      in under a different identity.

    Not found error
    * Visit https://qastaging.launchpad.net/+fnordbang
    * Verify the header is show with the Lp brand.
    * Verify the page states that something was not found

    Already logged in error
    * While logged in
      Visit https://qastaging.launchpad.net/~sinzui/+login
    * Verify the header is show with the Lp brand.

    Unexpected Form data
    * Visit https://qastaging.launchpad.net/ubuntu/+bugs?search=Search&field.status=me
    * Verify the header is show with the Lp brand.

    Batch Error
    * Visit https://qastaging.launchpad.net/projects/+all?start=0&batch=1000

    Translations not available error
    * As anon
      Visit https://translations.qastaging.launchpad.net/ubuntu/r-series
    * Verify the header is show with the Lp brand.

    Code of Conduct
    * Visit https://qastaging.launchpad.net/codeofconduct/1.1/+sign
    * Verify the header is show with the Lp brand.
    * Visit https://qastaging.launchpad.net/codeofconduct/1.0
    * Verify the header is show with the Lp brand.

    OAuth
    * Visit https://qastaging.launchpad.net/+authorize-token?oauth_token=
    * Verify the header is show with the Lp brand.
    * Verify the second heading is smaller than the title in the header.

    Login token
    * Visit https://qastaging.launchpad.net/token/P2k8cpzrSwDrnNrw82wq
    * Verify the header is show with the Lp brand.


LINT

    lib/lp/app/configure.zcml
    lib/lp/app/browser/configure.zcml
    lib/lp/app/browser/launchpad.py
    lib/lp/app/browser/tales.py
    lib/lp/app/browser/tests/base-layout.txt
    lib/lp/app/browser/tests/test_base_layout.py
    lib/lp/app/browser/tests/test_launchpad.py
    lib/lp/app/model/launchpad.py
    lib/lp/app/templates/base-layout.pt
    lib/lp/app/templates/launchpad-discoveryfailure.pt
    lib/lp/app/templates/launchpad-forbidden-macros.pt
    lib/lp/app/templates/launchpad-forbidden.pt
    lib/lp/app/templates/launchpad-gone.pt
    lib/lp/app/templates/launchpad-invalidbatchsize.pt
    lib/lp/app/templates/launchpad-noreferrer.pt
    lib/lp/app/templates/launchpad-notfound.pt
    lib/lp/app/templates/launchpad-readonlyfailure.pt
    lib/lp/app/templates/launchpad-requestexpired.pt
    lib/lp/app/templates/launchpad-translationunavailable.pt
    lib/lp/app/templates/launchpad-unexpectedformdata.pt
    lib/lp/app/templates/root-index.pt
    lib/lp/app/tests/test_launchpad.py
    lib/lp/registry/templates/codeofconduct-admin.pt
    lib/lp/registry/templates/codeofconduct-index.pt
    lib/lp/registry/templates/codeofconduct-list.pt
    lib/lp/registry/templates/signedcodeofconduct-add.pt
    lib/lp/registry/templates/signedcodeofconduct-index.pt
    lib/lp/services/oauth/templates/oauth-authorize.pt
    lib/lp/services/verification/templates/logintoken-claimteam.pt
    lib/lp/services/verification/templates/logintoken-index.pt
    lib/lp/services/verification/templates/logintoken-mergepeople.pt
    lib/lp/services/verification/templates/logintoken-validateemail.pt
    lib/lp/services/verification/templates/logintoken-validategpg.pt
    lib/lp/services/verification/templates/logintoken-validatesignonlygpg.pt
    lib/lp/services/verification/templates/logintoken-validateteamemail.pt
    lib/lp/services/webapp/templates/login-already.pt
    lib/lp/services/webapp/templates/login-error.pt
    lib/lp/services/webapp/templates/login-suspended-account.pt
    lib/lp/services/webapp/templates/login-team-email-address.pt
    lib/lp/testopenid/templates/auth.pt


TEST

    ./bin/test -vv -t test_launchpad -t layout lp.app
    ./bin/test -vvc -t xx-person-editgpg -t xx-add-email -t xx-team-claim \
        -t xx-team-contactemail -t login -t coc -oauth lp


IMPLEMENTATION

Added a ExceptionHierarchy to suppress breadcrumbs. The breadcrumbs can
disclose confidential information. The breadcrumbs are often wrong because
an error can be raised during the act of traversal. This was the underling
reason for using the locationless layout for for error pages.
    lib/lp/app/browser/configure.zcml
    lib/lp/app/browser/launchpad.py
    lib/lp/app/browser/tests/test_launchpad.py

Added a general Privacy adapter object and a specific one for Errors. The
existing adapters require the object to provide a .private attribute. Since
there are circumstances such as errors that require us to show the privacy
banner/presentation, we need a means to provide a private attribute. I added
the sharing explanation to the 403 error page.
    lib/lp/app/configure.zcml
    lib/lp/app/model/launchpad.py
    lib/lp/app/tests/test_launchpad.py
    lib/lp/app/templates/launchpad-forbidden-macros.pt

Remove support for locationless layout which is the only layout that
hid the application tabs and header.
    lib/lp/app/templates/base-layout.pt
    lib/lp/app/browser/tales.py
    lib/lp/app/browser/tests/base-layout.txt
    lib/lp/app/browser/tests/test_base_layout.py

Switched the Lp front page to use main_only layout and removed the Lp
logo because the page automatically shows the Lp branch and application
links.
    lib/lp/app/templates/root-index.pt

Update all locationless templates to be main_only. Most of the templates
are for errors or login tokens. All the code of conduct pages were missing
their headers.
    lib/lp/app/templates/launchpad-discoveryfailure.pt
    lib/lp/app/templates/launchpad-forbidden.pt
    lib/lp/app/templates/launchpad-gone.pt
    lib/lp/app/templates/launchpad-invalidbatchsize.pt
    lib/lp/app/templates/launchpad-noreferrer.pt
    lib/lp/app/templates/launchpad-notfound.pt
    lib/lp/app/templates/launchpad-readonlyfailure.pt
    lib/lp/app/templates/launchpad-requestexpired.pt
    lib/lp/app/templates/launchpad-translationunavailable.pt
    lib/lp/app/templates/launchpad-unexpectedformdata.pt
    lib/lp/registry/templates/codeofconduct-admin.pt
    lib/lp/registry/templates/codeofconduct-index.pt
    lib/lp/registry/templates/codeofconduct-list.pt
    lib/lp/registry/templates/signedcodeofconduct-add.pt
    lib/lp/registry/templates/signedcodeofconduct-index.pt
    lib/lp/services/oauth/templates/oauth-authorize.pt
    lib/lp/services/verification/templates/logintoken-claimteam.pt
    lib/lp/services/verification/templates/logintoken-index.pt
    lib/lp/services/verification/templates/logintoken-mergepeople.pt
    lib/lp/services/verification/templates/logintoken-validateemail.pt
    lib/lp/services/verification/templates/logintoken-validategpg.pt
    lib/lp/services/verification/templates/logintoken-validatesignonlygpg.pt
    lib/lp/services/verification/templates/logintoken-validateteamemail.pt
    lib/lp/services/webapp/templates/login-already.pt
    lib/lp/services/webapp/templates/login-error.pt
    lib/lp/services/webapp/templates/login-suspended-account.pt
    lib/lp/services/webapp/templates/login-team-email-address.pt
    lib/lp/testopenid/templates/auth.pt
-- 
https://code.launchpad.net/~sinzui/launchpad/error-pages/+merge/94574
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~sinzui/launchpad/error-pages into lp:launchpad.
=== modified file 'lib/lp/app/browser/configure.zcml'
--- lib/lp/app/browser/configure.zcml	2011-12-30 10:01:49 +0000
+++ lib/lp/app/browser/configure.zcml	2012-02-24 16:22:20 +0000
@@ -71,6 +71,14 @@
         permission="zope.Public"
         />
 
+    <browser:page
+        for="Exception"
+        name="+hierarchy"
+        class="lp.app.browser.launchpad.ExceptionHierarchy"
+        template="../templates/launchpad-hierarchy.pt"
+        permission="zope.Public"
+        />
+
     <!-- batching -->
     <browser:page
         for="lp.services.webapp.interfaces.IBatchNavigator"

=== modified file 'lib/lp/app/browser/launchpad.py'
--- lib/lp/app/browser/launchpad.py	2012-02-23 23:14:10 +0000
+++ lib/lp/app/browser/launchpad.py	2012-02-24 16:22:20 +0000
@@ -7,6 +7,7 @@
 __all__ = [
     'AppFrontPageSearchView',
     'DoesNotExistView',
+    'ExceptionHierarchy',
     'Hierarchy',
     'IcingFolder',
     'iter_view_registrations',
@@ -334,6 +335,14 @@
         return len(self.items) > 1 and not has_major_heading
 
 
+class ExceptionHierarchy(Hierarchy):
+
+    @property
+    def objects(self):
+        """Return an empty list because the traversal is not safe or sane."""
+        return []
+
+
 class Macro:
     """Keeps templates that are registered as pages from being URL accessable.
 

=== modified file 'lib/lp/app/browser/tales.py'
--- lib/lp/app/browser/tales.py	2012-02-22 21:21:01 +0000
+++ lib/lp/app/browser/tales.py	2012-02-24 16:22:20 +0000
@@ -2507,7 +2507,6 @@
         view/macro:page/main_side
         view/macro:page/main_only
         view/macro:page/searchless
-        view/macro:page/locationless
 
         view/macro:pagehas/applicationtabs
         view/macro:pagehas/globalsearch
@@ -2612,8 +2611,6 @@
                 applicationtabs=True,
                 globalsearch=False,
                 portlets=False),
-       'locationless':
-            LayoutElements(),
         }
 
 

=== modified file 'lib/lp/app/browser/tests/base-layout.txt'
--- lib/lp/app/browser/tests/base-layout.txt	2011-12-24 17:49:30 +0000
+++ lib/lp/app/browser/tests/base-layout.txt	2012-02-24 16:22:20 +0000
@@ -95,31 +95,6 @@
         Has side portlets: False
     ...
 
-The locationless template is intended for pages that provide content outside
-of normal application use. It is for exceptions. The epilogue and main
-slots are rendered. Global search and the application tabs are not included
-in the header. The layout does not have a heading slot to provide the h1
-or breadcrumbs; the template must provide its own h1 element.
-
-    >>> class LocationlessView(LaunchpadView):
-    ...     """A simple view to test base-layout."""
-    ...     __launchpad_facetname__ = 'overview'
-    ...     template = ViewPageTemplateFile('testfiles/locationless.pt')
-    ...     page_title = 'Test base-layout: locationless'
-
-    >>> view = LocationlessView(user, request)
-    >>> html = view.render()
-    >>> print html
-    <!DOCTYPE html ...
-    ...
-      <!--
-        Facet name: overview
-        Page type: locationless
-        Has global search: False
-        Has application tabs: False
-        Has side portlets: False
-    ...
-
 
 Page Diagnostics
 ----------------
@@ -130,9 +105,9 @@
     ...
     <!--
       Facet name: overview
-      Page type: locationless
+      Page type: searchless
       Has global search: False
-      Has application tabs: False
+      Has application tabs: True
       Has side portlets: False
       At least ... queries... issued in ... seconds
       Features: {...}
@@ -190,19 +165,19 @@
     >>> team = factory.makeTeam(
     ...     owner=user, name='a-private-team',
     ...     visibility=PersonVisibility.PRIVATE)
-    >>> view = LocationlessView(team, request)
+    >>> view = MainOnlyView(team, request)
     >>> body = find_tag_by_id(view.render(), 'document')
     >>> print body['class']
-    tab-overview locationless private yui3-skin-sam
+    tab-overview main_only private yui3-skin-sam
 
 When the context is public, the 'public' class is in the class attribute.
 
     >>> login(ANONYMOUS)
     >>> team = factory.makeTeam(owner=user, name='a-public-team')
-    >>> view = LocationlessView(team, request)
+    >>> view = MainOnlyView(team, request)
     >>> body = find_tag_by_id(view.render(), 'document')
     >>> print body['class']
-    tab-overview locationless public yui3-skin-sam
+    tab-overview main_only public yui3-skin-sam
 
 
 Notifications

=== modified file 'lib/lp/app/browser/tests/test_base_layout.py'
--- lib/lp/app/browser/tests/test_base_layout.py	2012-02-22 22:17:24 +0000
+++ lib/lp/app/browser/tests/test_base_layout.py	2012-02-24 16:22:20 +0000
@@ -171,21 +171,6 @@
         self.assertEqual(None, document.find(True, id='side-portlets'))
         self.assertEqual(None, document.find(True, id='globalsearch'))
 
-    def test_locationless(self):
-        # The locationless layout has no optional content.
-        view = self.makeTemplateView('locationless')
-        content = BeautifulSoup(view())
-        self.verify_base_layout_html_element(content)
-        self.verify_base_layout_head_parts(view, content)
-        document = find_tag_by_id(content, 'document')
-        self.verify_base_layout_body_parts(document)
-        classes = 'tab-overview locationless public yui3-skin-sam'.split()
-        self.assertEqual(classes, document['class'].split())
-        self.assertEqual(None, document.find(True, id='registration'))
-        self.assertEqual(None, document.find(True, id='watermark'))
-        self.assertEqual(None, document.find(True, id='side-portlets'))
-        self.assertEqual(None, document.find(True, id='globalsearch'))
-
     def test_contact_support_logged_in(self):
         # The support link points to /support when the user is logged in.
         view = self.makeTemplateView('main_only')
@@ -232,8 +217,8 @@
 
     def test_user_with_launchpad_view(self):
         # Users with launchpad.View do not see the sharing explanation.
-        # See the main_side, main_only, locationless, and searchless
-        # tests to know what content is provides to the user who can view.
+        # See the main_side, main_only, and searchless tests to know
+        # what content is provides to the user who can view.
         view = self.makeTemplateView('main_side')
         content = extract_text(find_tag_by_id(view(), 'maincontent'))
         self.assertNotIn(

=== modified file 'lib/lp/app/browser/tests/test_launchpad.py'
--- lib/lp/app/browser/tests/test_launchpad.py	2012-01-01 02:58:52 +0000
+++ lib/lp/app/browser/tests/test_launchpad.py	2012-02-24 16:22:20 +0000
@@ -433,6 +433,26 @@
         self.assertEqual(410, view.request.response.getStatus())
 
 
+class ExceptionHierarchyTestCase(TestCaseWithFactory):
+
+    layer = FunctionalLayer
+
+    def test_exception(self):
+        view = create_view(IndexError('test'), '+hierarchy')
+        view.request.traversed_objects = [getUtility(ILaunchpadRoot)]
+        self.assertEqual([], view.objects)
+
+    def test_zope_exception(self):
+        view = create_view(Unauthorized('test'), '+hierarchy')
+        view.request.traversed_objects = [getUtility(ILaunchpadRoot)]
+        self.assertEqual([], view.objects)
+
+    def test_launchapd_exception(self):
+        view = create_view(NotFound(None, 'test'), '+hierarchy')
+        view.request.traversed_objects = [getUtility(ILaunchpadRoot)]
+        self.assertEqual([], view.objects)
+
+
 class TestIterViewRegistrations(TestCaseWithFactory):
 
     layer = FunctionalLayer

=== removed file 'lib/lp/app/browser/tests/testfiles/locationless.pt'
--- lib/lp/app/browser/tests/testfiles/locationless.pt	2009-07-17 17:59:07 +0000
+++ lib/lp/app/browser/tests/testfiles/locationless.pt	1970-01-01 00:00:00 +0000
@@ -1,32 +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/locationless">
-
-  <head>
-    <tal:epilogue metal:fill-slot="head_epilogue">
-    <!-- Extra head content -->
-    </tal:epilogue>
-  </head>
-
-  <body>
-    <tal:main metal:fill-slot="main">
-      <div class="top-portlet">
-        <h1>Heading</h1>
-        <p class="registered">
-          Registered on 2005-09-16
-          by <a class="sprite team" href="#">Illuminati team</a>
-        </p>
-        <p>
-          Main content of the page.
-        </p>
-      </div>
-    </tal:main>
-
-    <tal:side metal:fill-slot="side">
-      THIS MUST NOT RENDER
-    </tal:side>
-  </body>
-</html>

=== modified file 'lib/lp/app/configure.zcml'
--- lib/lp/app/configure.zcml	2012-02-23 21:00:22 +0000
+++ lib/lp/app/configure.zcml	2012-02-24 16:22:20 +0000
@@ -43,6 +43,13 @@
         factory="lp.app.model.cdatetime.AgingAdapter" />
 
     <adapter
+        provides="lp.app.interfaces.launchpad.IPrivacy"
+        for="Exception"
+        factory="lp.app.model.launchpad.ExceptionPrivacy"
+        permission="zope.Public"
+        />
+
+    <adapter
         provides="lp.services.privacy.interfaces.IObjectPrivacy"
         for="zope.interface.Interface"
         factory="lp.services.privacy.adapters.ObjectPrivacy"

=== added file 'lib/lp/app/model/launchpad.py'
--- lib/lp/app/model/launchpad.py	1970-01-01 00:00:00 +0000
+++ lib/lp/app/model/launchpad.py	2012-02-24 16:22:20 +0000
@@ -0,0 +1,39 @@
+# Copyright 2012 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Common implementation ofinterfaces.launchpad interfaces."""
+
+__metaclass__ = type
+
+__all__ = [
+    'ExceptionPrivacy',
+    'Privacy',
+    ]
+
+from zope.interface import implements
+from zope.security.interfaces import (
+    Forbidden,
+    ForbiddenAttribute,
+    Unauthorized,
+    )
+from lp.app.interfaces.launchpad import IPrivacy
+
+
+class Privacy:
+    """Represent any object as IPrivacy."""
+    implements(IPrivacy)
+
+    def __init__(self, context, private):
+        self.context = context
+        self.private = private
+
+
+class ExceptionPrivacy(Privacy):
+    """Adapt an Exception to IPrivacy."""
+
+    def __init__(self, error):
+        if isinstance(error, (Forbidden, ForbiddenAttribute, Unauthorized)):
+            private = True
+        else:
+            private = False
+        super(ExceptionPrivacy, self).__init__(error, private)

=== modified file 'lib/lp/app/templates/base-layout.pt'
--- lib/lp/app/templates/base-layout.pt	2012-02-16 13:25:17 +0000
+++ lib/lp/app/templates/base-layout.pt	2012-02-24 16:22:20 +0000
@@ -88,8 +88,7 @@
         <tal:login replace="structure context/@@login_status" />
       </div><!--id="locationbar"-->
 
-      <div id="watermark" class="watermark-apps-portlet"
-           tal:condition="view/macro:pagehas/applicationtabs">
+      <div id="watermark" class="watermark-apps-portlet">
         <div class="flowed-block">
           <span tal:replace="structure view/watermark:logo"></span>
         </div>
@@ -110,8 +109,7 @@
               lang view/lang|default_language|default;
               xml:lang view/lang|default_language|default;
               dir view/dir|string:ltr">
-            <div class="context-publication"
-              tal:condition="view/macro:pagehas/applicationtabs">
+            <div class="context-publication">
               <h1
                 tal:condition="view/label|nothing"
                 tal:content="view/label"

=== modified file 'lib/lp/app/templates/launchpad-discoveryfailure.pt'
--- lib/lp/app/templates/launchpad-discoveryfailure.pt	2011-06-10 19:36:32 +0000
+++ lib/lp/app/templates/launchpad-discoveryfailure.pt	2012-02-24 16:22:20 +0000
@@ -3,7 +3,7 @@
   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/locationless"
+  metal:use-macro="view/macro:page/main_only"
   i18n:domain="launchpad"
 >
   <body>

=== modified file 'lib/lp/app/templates/launchpad-forbidden-macros.pt'
--- lib/lp/app/templates/launchpad-forbidden-macros.pt	2011-08-31 16:00:31 +0000
+++ lib/lp/app/templates/launchpad-forbidden-macros.pt	2012-02-24 16:22:20 +0000
@@ -4,7 +4,8 @@
   xmlns:i18n="http://xml.zope.org/namespaces/i18n";
   define-macro="body">
   <p>
-    Sorry, you don't have permission to access this page.
+    Sorry, you don't have permission to access this page or
+    the information in this page is not shared with you.
   </p>
   <div tal:condition="request/lp:person">
   <p>

=== modified file 'lib/lp/app/templates/launchpad-forbidden.pt'
--- lib/lp/app/templates/launchpad-forbidden.pt	2011-05-27 20:26:32 +0000
+++ lib/lp/app/templates/launchpad-forbidden.pt	2012-02-24 16:22:20 +0000
@@ -3,10 +3,10 @@
   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/locationless"
+  metal:use-macro="view/macro:page/main_only"
   i18n:domain="launchpad"
 >
-  <body>
+  <body class="private">
     <div class="top-portlet" metal:fill-slot="main">
       <h1>Not allowed here</h1>
       <span metal:use-macro="context/@@+forbidden-page-macros/body" />

=== modified file 'lib/lp/app/templates/launchpad-gone.pt'
--- lib/lp/app/templates/launchpad-gone.pt	2011-12-24 17:49:30 +0000
+++ lib/lp/app/templates/launchpad-gone.pt	2012-02-24 16:22:20 +0000
@@ -3,7 +3,7 @@
   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/locationless"
+  metal:use-macro="view/macro:page/main_only"
   i18n:domain="launchpad">
   <body>
     <div class="top-portlet" metal:fill-slot="main">

=== modified file 'lib/lp/app/templates/launchpad-invalidbatchsize.pt'
--- lib/lp/app/templates/launchpad-invalidbatchsize.pt	2011-05-27 20:26:32 +0000
+++ lib/lp/app/templates/launchpad-invalidbatchsize.pt	2012-02-24 16:22:20 +0000
@@ -3,7 +3,7 @@
   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/locationless"
+  metal:use-macro="view/macro:page/main_only"
   i18n:domain="launchpad"
 >
   <body>

=== modified file 'lib/lp/app/templates/launchpad-noreferrer.pt'
--- lib/lp/app/templates/launchpad-noreferrer.pt	2011-05-27 20:26:32 +0000
+++ lib/lp/app/templates/launchpad-noreferrer.pt	2012-02-24 16:22:20 +0000
@@ -3,7 +3,7 @@
   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/locationless"
+  metal:use-macro="view/macro:page/main_only"
   i18n:domain="launchpad"
 >
   <body>

=== modified file 'lib/lp/app/templates/launchpad-notfound.pt'
--- lib/lp/app/templates/launchpad-notfound.pt	2012-02-10 09:40:31 +0000
+++ lib/lp/app/templates/launchpad-notfound.pt	2012-02-24 16:22:20 +0000
@@ -3,7 +3,7 @@
   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/locationless"
+  metal:use-macro="view/macro:page/main_only"
   i18n:domain="launchpad"
 >
   <body>

=== modified file 'lib/lp/app/templates/launchpad-readonlyfailure.pt'
--- lib/lp/app/templates/launchpad-readonlyfailure.pt	2011-05-27 20:26:32 +0000
+++ lib/lp/app/templates/launchpad-readonlyfailure.pt	2012-02-24 16:22:20 +0000
@@ -3,7 +3,7 @@
   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/locationless"
+  metal:use-macro="view/macro:page/main_only"
   i18n:domain="launchpad"
 >
   <body>

=== modified file 'lib/lp/app/templates/launchpad-requestexpired.pt'
--- lib/lp/app/templates/launchpad-requestexpired.pt	2011-05-27 20:26:32 +0000
+++ lib/lp/app/templates/launchpad-requestexpired.pt	2012-02-24 16:22:20 +0000
@@ -3,7 +3,7 @@
   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/locationless"
+  metal:use-macro="view/macro:page/main_only"
   i18n:domain="launchpad"
 >
   <body>

=== modified file 'lib/lp/app/templates/launchpad-translationunavailable.pt'
--- lib/lp/app/templates/launchpad-translationunavailable.pt	2011-05-27 20:26:32 +0000
+++ lib/lp/app/templates/launchpad-translationunavailable.pt	2012-02-24 16:22:20 +0000
@@ -3,7 +3,7 @@
   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/locationless"
+  metal:use-macro="view/macro:page/main_only"
   i18n:domain="launchpad"
 >
 

=== modified file 'lib/lp/app/templates/launchpad-unexpectedformdata.pt'
--- lib/lp/app/templates/launchpad-unexpectedformdata.pt	2011-05-27 20:26:32 +0000
+++ lib/lp/app/templates/launchpad-unexpectedformdata.pt	2012-02-24 16:22:20 +0000
@@ -3,7 +3,7 @@
   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/locationless"
+  metal:use-macro="view/macro:page/main_only"
   i18n:domain="launchpad"
 >
   <body>

=== modified file 'lib/lp/app/templates/root-index.pt'
--- lib/lp/app/templates/root-index.pt	2012-02-01 15:31:32 +0000
+++ lib/lp/app/templates/root-index.pt	2012-02-24 16:22:20 +0000
@@ -3,7 +3,7 @@
   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/locationless"
+  metal:use-macro="view/macro:page/main_only"
   i18n:domain="launchpad">
   <metal:head fill-slot="head_epilogue">
     <style>
@@ -57,13 +57,6 @@
       <!-- Is your project registered yet? -->
 
       <div id="homepage" class="homepage">
-
-        <div class="top-portlet" style="border-bottom: 1px dotted #999;">
-          <img src="/@@/launchpad-logo-and-name.png"
-               alt=""
-               style="margin: 0 9em 1em 0"/>
-        </div>
-
         <div class="yui-g">
           <div class="yui-u first" style="margin-top: 1.5em;">
             <div class="homepage-whatslaunchpad"

=== added file 'lib/lp/app/tests/test_launchpad.py'
--- lib/lp/app/tests/test_launchpad.py	1970-01-01 00:00:00 +0000
+++ lib/lp/app/tests/test_launchpad.py	2012-02-24 16:22:20 +0000
@@ -0,0 +1,60 @@
+# Copyright 2012 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Tests for launchpad classes."""
+
+__metaclass__ = type
+
+from zope.security.interfaces import (
+    Forbidden,
+    ForbiddenAttribute,
+    Unauthorized,
+    )
+
+from lp.app.interfaces.launchpad import (
+    IPrivacy,
+    )
+from lp.app.model.launchpad import (
+    ExceptionPrivacy,
+    Privacy,
+    )
+from lp.testing import TestCase
+from lp.testing.layers import FunctionalLayer
+
+
+class PrivacyTestCase(TestCase):
+
+    layer = FunctionalLayer
+
+    def test_init(self):
+        thing = ['any', 'thing']
+        privacy = Privacy(thing, True)
+        self.assertIs(True, IPrivacy.providedBy(privacy))
+        self.assertIs(True, privacy.private)
+        privacy = Privacy(thing, False)
+        self.assertIs(False, privacy.private)
+
+
+class ExceptionPrivacyTestCase(TestCase):
+
+    layer = FunctionalLayer
+
+    def test_exception(self):
+        privacy = IPrivacy(IndexError('test'))
+        self.assertIs(True, IPrivacy.providedBy(privacy))
+        self.assertIs(False, privacy.private)
+
+    def test_unauthorized(self):
+        privacy = IPrivacy(Unauthorized('test'))
+        self.assertIs(True, IPrivacy.providedBy(privacy))
+        self.assertIs(True, privacy.private)
+
+    def test_forbidden(self):
+        privacy = IPrivacy(Forbidden('test'))
+        self.assertIs(True, IPrivacy.providedBy(privacy))
+        self.assertIs(True, privacy.private)
+
+    def test_forbidden_attribute(self):
+        privacy = IPrivacy(ForbiddenAttribute('test'))
+        self.assertIs(True, IPrivacy.providedBy(privacy))
+        self.assertIs(True, privacy.private)

=== modified file 'lib/lp/registry/templates/codeofconduct-admin.pt'
--- lib/lp/registry/templates/codeofconduct-admin.pt	2009-08-17 15:36:10 +0000
+++ lib/lp/registry/templates/codeofconduct-admin.pt	2012-02-24 16:22:20 +0000
@@ -3,7 +3,7 @@
   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/locationless"
+  metal:use-macro="view/macro:page/main_only"
   i18n:domain="launchpad"
 >
   <body>

=== modified file 'lib/lp/registry/templates/codeofconduct-index.pt'
--- lib/lp/registry/templates/codeofconduct-index.pt	2009-08-18 17:15:10 +0000
+++ lib/lp/registry/templates/codeofconduct-index.pt	2012-02-24 16:22:20 +0000
@@ -3,7 +3,7 @@
   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/locationless"
+  metal:use-macro="view/macro:page/main_only"
   i18n:domain="launchpad"
 >
   <body>

=== modified file 'lib/lp/registry/templates/codeofconduct-list.pt'
--- lib/lp/registry/templates/codeofconduct-list.pt	2010-04-12 20:26:24 +0000
+++ lib/lp/registry/templates/codeofconduct-list.pt	2012-02-24 16:22:20 +0000
@@ -3,7 +3,7 @@
   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/locationless"
+  metal:use-macro="view/macro:page/main_only"
   i18n:domain="launchpad"
 >
   <body>

=== modified file 'lib/lp/registry/templates/signedcodeofconduct-add.pt'
--- lib/lp/registry/templates/signedcodeofconduct-add.pt	2010-03-23 19:06:56 +0000
+++ lib/lp/registry/templates/signedcodeofconduct-add.pt	2012-02-24 16:22:20 +0000
@@ -3,7 +3,7 @@
   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/locationless"
+  metal:use-macro="view/macro:page/main_only"
   i18n:domain="launchpad"
 >
   <body>

=== modified file 'lib/lp/registry/templates/signedcodeofconduct-index.pt'
--- lib/lp/registry/templates/signedcodeofconduct-index.pt	2009-08-17 15:36:10 +0000
+++ lib/lp/registry/templates/signedcodeofconduct-index.pt	2012-02-24 16:22:20 +0000
@@ -3,7 +3,7 @@
   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/locationless"
+  metal:use-macro="view/macro:page/main_only"
   i18n:domain="launchpad"
 >
 <body>

=== modified file 'lib/lp/services/oauth/templates/oauth-authorize.pt'
--- lib/lp/services/oauth/templates/oauth-authorize.pt	2011-12-08 05:48:32 +0000
+++ lib/lp/services/oauth/templates/oauth-authorize.pt	2012-02-24 16:22:20 +0000
@@ -3,13 +3,13 @@
   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/locationless"
+  metal:use-macro="view/macro:page/main_only"
   i18n:domain="launchpad"
 >
 <body>
   <div class="top-portlet" metal:fill-slot="main" tal:define="token view/token">
     <tal:no-token condition="not:token">
-      <h1>Unable to identify application</h1>
+      <h2>Unable to identify application</h2>
       <p>
         The information provided by the remote application was incorrect or
         incomplete. Because of that we were unable to identify the
@@ -28,7 +28,7 @@
           <div metal:fill-slot="extra_top">
 
            <tal:desktop-integration-token condition="token/consumer/is_integrated_desktop">
-             <h1>Confirm Computer Access</h1>
+             <h2>Confirm Computer Access</h2>
              <p>The
                <tal:desktop replace="structure
                token/consumer/integrated_desktop_type" />
@@ -78,9 +78,9 @@
            </tal:desktop-integration-token>
 
            <tal:web-integration-token condition="not:token/consumer/is_integrated_desktop">
-             <h1>Integrating
+             <h2>Integrating
                <tal:hostname replace="structure token/consumer/key" />
-               into your Launchpad account</h1>
+               into your Launchpad account</h2>
 
              <p>The application identified as
                <strong tal:content="token/consumer/key">consumer</strong>
@@ -134,7 +134,7 @@
 
           <tal:unauthorized
              condition="view/token/permission/enumvalue:UNAUTHORIZED">
-            <h1>You decided against desktop integration</h1>
+            <h2>You decided against desktop integration</h2>
 
             <p>
               You decided not to give
@@ -147,7 +147,7 @@
 
           <tal:authorized
              condition="not:view/token/permission/enumvalue:UNAUTHORIZED">
-            <h1>Almost finished ...</h1>
+            <h2>Almost finished ...</h2>
 
             <p>
               The
@@ -179,7 +179,7 @@
 
           <tal:unauthorized
              condition="view/token/permission/enumvalue:UNAUTHORIZED">
-            <h1>Access not granted to application</h1>
+            <h2>Access not granted to application</h2>
             <p>
               The application identified as
               <strong tal:content="view/token/consumer/key">key</strong> has not
@@ -189,7 +189,7 @@
 
           <tal:authorized
              condition="not:view/token/permission/enumvalue:UNAUTHORIZED">
-            <h1>Almost finished ...</h1>
+            <h2>Almost finished ...</h2>
             <p>
               To finish authorizing the application identified as
               <strong tal:content="view/token/consumer/key">key</strong>

=== modified file 'lib/lp/services/verification/templates/logintoken-claimteam.pt'
--- lib/lp/services/verification/templates/logintoken-claimteam.pt	2011-12-20 10:21:46 +0000
+++ lib/lp/services/verification/templates/logintoken-claimteam.pt	2012-02-24 16:22:20 +0000
@@ -3,7 +3,7 @@
   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/locationless"
+  metal:use-macro="view/macro:page/main_only"
   i18n:domain="launchpad"
 >
 <body>

=== modified file 'lib/lp/services/verification/templates/logintoken-index.pt'
--- lib/lp/services/verification/templates/logintoken-index.pt	2011-12-20 10:21:46 +0000
+++ lib/lp/services/verification/templates/logintoken-index.pt	2012-02-24 16:22:20 +0000
@@ -3,7 +3,7 @@
   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/locationless"
+  metal:use-macro="view/macro:page/main_only"
   i18n:domain="launchpad"
 >
 

=== modified file 'lib/lp/services/verification/templates/logintoken-mergepeople.pt'
--- lib/lp/services/verification/templates/logintoken-mergepeople.pt	2011-12-20 10:21:46 +0000
+++ lib/lp/services/verification/templates/logintoken-mergepeople.pt	2012-02-24 16:22:20 +0000
@@ -6,7 +6,7 @@
 <tal:do-this-first tal:content="view/processForm" />
 
 <html
-  metal:use-macro="view/macro:page/locationless"
+  metal:use-macro="view/macro:page/main_only"
 >
 <body>
   <div class="top-portlet" metal:fill-slot="main">

=== modified file 'lib/lp/services/verification/templates/logintoken-validateemail.pt'
--- lib/lp/services/verification/templates/logintoken-validateemail.pt	2011-12-20 10:21:46 +0000
+++ lib/lp/services/verification/templates/logintoken-validateemail.pt	2012-02-24 16:22:20 +0000
@@ -3,7 +3,7 @@
   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/locationless"
+  metal:use-macro="view/macro:page/main_only"
   i18n:domain="launchpad"
 >
   <body>

=== modified file 'lib/lp/services/verification/templates/logintoken-validategpg.pt'
--- lib/lp/services/verification/templates/logintoken-validategpg.pt	2011-12-20 10:21:46 +0000
+++ lib/lp/services/verification/templates/logintoken-validategpg.pt	2012-02-24 16:22:20 +0000
@@ -3,7 +3,7 @@
   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/locationless"
+  metal:use-macro="view/macro:page/main_only"
   i18n:domain="launchpad"
 >
   <body>

=== modified file 'lib/lp/services/verification/templates/logintoken-validatesignonlygpg.pt'
--- lib/lp/services/verification/templates/logintoken-validatesignonlygpg.pt	2011-12-20 10:21:46 +0000
+++ lib/lp/services/verification/templates/logintoken-validatesignonlygpg.pt	2012-02-24 16:22:20 +0000
@@ -3,7 +3,7 @@
   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/locationless"
+  metal:use-macro="view/macro:page/main_only"
   i18n:domain="launchpad"
 >
   <body>

=== modified file 'lib/lp/services/verification/templates/logintoken-validateteamemail.pt'
--- lib/lp/services/verification/templates/logintoken-validateteamemail.pt	2011-12-20 10:21:46 +0000
+++ lib/lp/services/verification/templates/logintoken-validateteamemail.pt	2012-02-24 16:22:20 +0000
@@ -3,7 +3,7 @@
   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/locationless"
+  metal:use-macro="view/macro:page/main_only"
   i18n:domain="launchpad"
 >
   <body>

=== modified file 'lib/lp/services/webapp/templates/login-already.pt'
--- lib/lp/services/webapp/templates/login-already.pt	2011-05-27 21:03:22 +0000
+++ lib/lp/services/webapp/templates/login-already.pt	2012-02-24 16:22:20 +0000
@@ -3,7 +3,7 @@
   xmlns:metal="http://xml.zope.org/namespaces/metal";
   xmlns:i18n="http://xml.zope.org/namespaces/i18n";
   omit-tag="">
-<html metal:use-macro="view/macro:page/locationless">
+<html metal:use-macro="view/macro:page/main_only">
   <body>
     <div class="top-portlet" metal:fill-slot="main">
       <h1>You are already logged in</h1>

=== modified file 'lib/lp/services/webapp/templates/login-error.pt'
--- lib/lp/services/webapp/templates/login-error.pt	2011-05-27 21:03:22 +0000
+++ lib/lp/services/webapp/templates/login-error.pt	2012-02-24 16:22:20 +0000
@@ -3,12 +3,12 @@
   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/locationless"
+  metal:use-macro="view/macro:page/main_only"
   i18n:domain="launchpad">
 
   <body>
     <div class="top-portlet" metal:fill-slot="main">
-    
+
       <h1>Your login was unsuccessful</h1>
       <p class="error" tal:content="view/login_error" />
 

=== modified file 'lib/lp/services/webapp/templates/login-suspended-account.pt'
--- lib/lp/services/webapp/templates/login-suspended-account.pt	2011-05-27 21:03:22 +0000
+++ lib/lp/services/webapp/templates/login-suspended-account.pt	2012-02-24 16:22:20 +0000
@@ -3,12 +3,12 @@
   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/locationless"
+  metal:use-macro="view/macro:page/main_only"
   i18n:domain="launchpad">
 
   <body>
     <div class="top-portlet" metal:fill-slot="main">
-    
+
       <h1>This account has been suspended</h1>
       <p class="error">
         Contact a <a href="mailto:feedback@xxxxxxxxxxxxx?subject=SUSPENDED%20account";

=== modified file 'lib/lp/services/webapp/templates/login-team-email-address.pt'
--- lib/lp/services/webapp/templates/login-team-email-address.pt	2012-01-20 06:29:10 +0000
+++ lib/lp/services/webapp/templates/login-team-email-address.pt	2012-02-24 16:22:20 +0000
@@ -3,7 +3,7 @@
   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/locationless"
+  metal:use-macro="view/macro:page/main_only"
   i18n:domain="launchpad">
 
   <body>

=== modified file 'lib/lp/testopenid/templates/auth.pt'
--- lib/lp/testopenid/templates/auth.pt	2010-01-21 17:34:51 +0000
+++ lib/lp/testopenid/templates/auth.pt	2012-02-24 16:22:20 +0000
@@ -6,7 +6,7 @@
   xml:lang="en"
   lang="en"
   dir="ltr"
-  metal:use-macro="view/macro:page/locationless"
+  metal:use-macro="view/macro:page/main_only"
   i18n:domain="launchpad"
 >
   <body>