← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~wgrant/launchpad/ifacet into lp:launchpad

 

William Grant has proposed merging lp:~wgrant/launchpad/ifacet into lp:launchpad.

Commit message:
Provide IFacet utilities describing the default view name, title and rootsite. Use them to generate breadcrumbs, and use a new IMultiFacetedBreadcrumb to decide where to place the facet breadcrumb.

Requested reviews:
  William Grant (wgrant): code

For more details, see:
https://code.launchpad.net/~wgrant/launchpad/ifacet/+merge/242619
-- 
Your team Launchpad code reviewers is subscribed to branch lp:launchpad.
=== modified file 'lib/lp/answers/browser/questiontarget.py'
--- lib/lp/answers/browser/questiontarget.py	2014-04-24 02:53:05 +0000
+++ lib/lp/answers/browser/questiontarget.py	2014-11-24 06:00:00 +0000
@@ -6,7 +6,6 @@
 __metaclass__ = type
 
 __all__ = [
-    'AnswersFacetBreadcrumb',
     'AskAQuestionButtonPortlet',
     'ManageAnswerContactView',
     'SearchQuestionsView',
@@ -85,7 +84,6 @@
     )
 from lp.services.webapp.authorization import check_permission
 from lp.services.webapp.batching import BatchNavigator
-from lp.services.webapp.breadcrumb import Breadcrumb
 from lp.services.webapp.escaping import structured
 from lp.services.webapp.publisher import LaunchpadView
 from lp.services.worlddata.helpers import (
@@ -960,8 +958,3 @@
         """Return a link to the manage answer contact view."""
         text = 'Set answer contact'
         return Link('+answer-contact', text, icon='edit')
-
-
-class AnswersFacetBreadcrumb(Breadcrumb):
-    rootsite = 'answers'
-    text = 'Questions'

=== modified file 'lib/lp/answers/configure.zcml'
--- lib/lp/answers/configure.zcml	2014-04-24 08:21:42 +0000
+++ lib/lp/answers/configure.zcml	2014-11-24 06:00:00 +0000
@@ -18,6 +18,10 @@
       component="lp.answers.publisher.AnswersLayer"
       provides="zope.publisher.interfaces.browser.IDefaultBrowserLayer"
       name="answers" />
+  <utility
+      component="lp.answers.publisher.AnswersFacet"
+      provides="lp.services.webapp.interfaces.IFacet"
+      name="answers" />
 
     <subscriber
         for=".interfaces.question.IQuestion
@@ -257,26 +261,7 @@
     <allow interface="lp.services.webapp.vocabulary.IHugeVocabulary"/>
   </class>
 
-
   <lp:help-folder folder="help" name="+help-answers"/>
-  <adapter
-      name="answers"
-      provides="lp.services.webapp.interfaces.IBreadcrumb"
-      for="lp.answers.interfaces.questiontarget.IQuestionTarget"
-      factory="lp.answers.browser.questiontarget.AnswersFacetBreadcrumb"
-      permission="zope.Public"/>
-  <adapter
-      name="answers"
-      provides="lp.services.webapp.interfaces.IBreadcrumb"
-      for="lp.registry.interfaces.projectgroup.IProjectGroup"
-      factory="lp.answers.browser.questiontarget.AnswersFacetBreadcrumb"
-      permission="zope.Public"/>
-  <adapter
-      name="answers"
-      provides="lp.services.webapp.interfaces.IBreadcrumb"
-      for="lp.registry.interfaces.person.IPerson"
-      factory="lp.answers.browser.questiontarget.AnswersFacetBreadcrumb"
-      permission="zope.Public"/>
 
   <webservice:register module="lp.answers.interfaces.webservice" />
 </configure>

=== modified file 'lib/lp/answers/publisher.py'
--- lib/lp/answers/publisher.py	2011-12-24 17:49:30 +0000
+++ lib/lp/answers/publisher.py	2014-11-24 06:00:00 +0000
@@ -6,6 +6,7 @@
 __metaclass__ = type
 __all__ = [
     'AnswersBrowserRequest',
+    'AnswersFacet',
     'AnswersLayer',
     'answers_request_publication_factory',
     ]
@@ -17,6 +18,7 @@
     IDefaultBrowserLayer,
     )
 
+from lp.services.webapp.interfaces import IFacet
 from lp.services.webapp.publication import LaunchpadBrowserPublication
 from lp.services.webapp.servers import (
     LaunchpadBrowserRequest,
@@ -24,6 +26,15 @@
     )
 
 
+class AnswersFacet:
+    implements(IFacet)
+
+    name = "answers"
+    rootsite = "answers"
+    text = "Questions"
+    default_view = "+questions"
+
+
 class AnswersLayer(IBrowserRequest, IDefaultBrowserLayer):
     """The Answers layer."""
 

=== modified file 'lib/lp/app/browser/launchpad.py'
--- lib/lp/app/browser/launchpad.py	2014-11-17 18:36:16 +0000
+++ lib/lp/app/browser/launchpad.py	2014-11-24 06:00:00 +0000
@@ -35,6 +35,7 @@
     getGlobalSiteManager,
     getUtility,
     queryAdapter,
+    queryUtility,
     )
 from zope.datetime import (
     DateTimeError,
@@ -143,8 +144,10 @@
 from lp.services.webapp.interfaces import (
     IBreadcrumb,
     ICanonicalUrlData,
+    IFacet,
     ILaunchBag,
     ILaunchpadRoot,
+    IMultiFacetedBreadcrumb,
     INavigationMenu,
     )
 from lp.services.webapp.menu import get_facet
@@ -253,8 +256,6 @@
 class Hierarchy(LaunchpadView):
     """The hierarchy part of the location bar on each page."""
 
-    vhost_breadcrumb = True
-
     @property
     def objects(self):
         """The objects for which we want breadcrumbs."""
@@ -294,20 +295,21 @@
             if breadcrumb is not None:
                 breadcrumbs.append(breadcrumb)
 
-        facet = get_facet(self._naked_context_view)
-        if (len(breadcrumbs) != 0 and facet is not None and
-            self.vhost_breadcrumb):
+        facet = queryUtility(IFacet, name=get_facet(self._naked_context_view))
+        if breadcrumbs and facet is not None:
             # We have breadcrumbs and we're on a custom facet, so we'll
             # sneak an extra breadcrumb for the facet we're on.
-
             # Iterate over the context of our breadcrumbs in reverse order and
-            # for the first one we find an adapter named after the facet we're
-            # on, generate an extra breadcrumb and insert it in our list.
+            # find the first one that implements IMultiFactedBreadcrumb.
+            # It'll be facet-agnostic, so insert a facet-specific one
+            # after it.
             for idx, breadcrumb in reversed(list(enumerate(breadcrumbs))):
-                extra_breadcrumb = queryAdapter(
-                    breadcrumb.context, IBreadcrumb, name=facet)
-                if extra_breadcrumb is not None:
-                    breadcrumbs.insert(idx + 1, extra_breadcrumb)
+                if IMultiFacetedBreadcrumb.providedBy(breadcrumb):
+                    breadcrumbs.insert(
+                        idx + 1,
+                        Breadcrumb(
+                            breadcrumb.context, rootsite=facet.rootsite,
+                            text=facet.text))
                     break
         if len(breadcrumbs) > 0:
             page_crumb = self.makeBreadcrumbForRequestedPage()
@@ -333,26 +335,18 @@
         URL and the page's name (i.e. the last path segment of the URL).
 
         If the view is the default one for the object or the current
-        facet, return None -- we'll have injected a *FacetBreadcrumb
+        facet, return None -- we'll have injected a facet Breadcrumb
         earlier in the hierarchy which links here.
         """
-        # XXX wgrant 2014-02-25: We should eventually define the
-        # facet-level defaults in app-level ZCML rather than hardcoding
-        # them centrally.
-        facet_defaults = {
-            'answers': '+questions',
-            'branches': '+branches',
-            'bugs': '+bugs',
-            'specifications': '+specs',
-            'translations': '+translations',
-            }
-
         url = self.request.getURL()
         obj = self.request.traversed_objects[-2]
         default_view_name = getDefaultViewName(obj, self.request)
         view = self._naked_context_view
-        facet = get_facet(view)
-        if view.__name__ not in (default_view_name, facet_defaults.get(facet)):
+        default_views = [default_view_name]
+        facet = queryUtility(IFacet, name=get_facet(view))
+        if facet is not None:
+            default_views.append(facet.default_view)
+        if view.__name__ not in default_views:
             title = getattr(view, 'page_title', None)
             if title is None:
                 title = getattr(view, 'label', None)

=== modified file 'lib/lp/blueprints/browser/configure.zcml'
--- lib/lp/blueprints/browser/configure.zcml	2014-02-26 03:05:44 +0000
+++ lib/lp/blueprints/browser/configure.zcml	2014-11-24 06:00:00 +0000
@@ -9,20 +9,6 @@
     xmlns:xmlrpc="http://namespaces.zope.org/xmlrpc";
     i18n_domain="launchpad">
 
-  <adapter
-      name="specifications"
-      provides="lp.services.webapp.interfaces.IBreadcrumb"
-      for="lp.blueprints.interfaces.specificationtarget.IHasSpecifications"
-      factory="lp.blueprints.browser.specificationtarget.BlueprintsFacetBreadcrumb"
-      permission="zope.Public"/>
-  <adapter
-      name="specifications"
-      provides="lp.services.webapp.interfaces.IBreadcrumb"
-      for="lp.registry.interfaces.person.IPerson"
-      factory="lp.blueprints.browser.specificationtarget.BlueprintsFacetBreadcrumb"
-      permission="zope.Public"/>
-
-
     <browser:navigation
         module="lp.blueprints.browser.sprint"
         classes="

=== modified file 'lib/lp/blueprints/browser/specificationtarget.py'
--- lib/lp/blueprints/browser/specificationtarget.py	2014-02-19 00:35:25 +0000
+++ lib/lp/blueprints/browser/specificationtarget.py	2014-11-24 06:00:00 +0000
@@ -56,7 +56,6 @@
     )
 from lp.services.webapp.authorization import check_permission
 from lp.services.webapp.batching import BatchNavigator
-from lp.services.webapp.breadcrumb import Breadcrumb
 from lp.services.webapp.menu import (
     enabled_with_permission,
     Link,
@@ -461,8 +460,3 @@
               </ul>
             </div>
             """ % self.target_url
-
-
-class BlueprintsFacetBreadcrumb(Breadcrumb):
-    rootsite = 'blueprints'
-    text = 'Blueprints'

=== modified file 'lib/lp/blueprints/browser/sprint.py'
--- lib/lp/blueprints/browser/sprint.py	2014-11-17 18:36:16 +0000
+++ lib/lp/blueprints/browser/sprint.py	2014-11-24 06:00:00 +0000
@@ -84,6 +84,7 @@
     Breadcrumb,
     TitleBreadcrumb,
     )
+from lp.services.webapp.interfaces import IMultiFacetedBreadcrumb
 
 
 class SprintFacets(StandardLaunchpadFacets):
@@ -102,7 +103,7 @@
 
 
 class SprintBreadcrumb(TitleBreadcrumb):
-    implements(IHeadingBreadcrumb)
+    implements(IHeadingBreadcrumb, IMultiFacetedBreadcrumb)
 
 
 class SprintOverviewMenu(NavigationMenu):

=== modified file 'lib/lp/blueprints/configure.zcml'
--- lib/lp/blueprints/configure.zcml	2014-11-17 18:36:16 +0000
+++ lib/lp/blueprints/configure.zcml	2014-11-24 06:00:00 +0000
@@ -21,7 +21,10 @@
       component="lp.blueprints.publisher.BlueprintsLayer"
       provides="zope.publisher.interfaces.browser.IDefaultBrowserLayer"
       name="blueprints" />
-
+  <utility
+      component="lp.blueprints.publisher.SpecificationsFacet"
+      provides="lp.services.webapp.interfaces.IFacet"
+      name="specifications" />
 
   <lp:help-folder folder="help" name="+help-blueprints" />
 

=== modified file 'lib/lp/blueprints/publisher.py'
--- lib/lp/blueprints/publisher.py	2011-12-24 17:49:30 +0000
+++ lib/lp/blueprints/publisher.py	2014-11-24 06:00:00 +0000
@@ -8,6 +8,7 @@
     'BlueprintsBrowserRequest',
     'BlueprintsLayer',
     'blueprints_request_publication_factory',
+    'SpecificationsFacet',
     ]
 
 
@@ -17,6 +18,7 @@
     IDefaultBrowserLayer,
     )
 
+from lp.services.webapp.interfaces import IFacet
 from lp.services.webapp.publication import LaunchpadBrowserPublication
 from lp.services.webapp.servers import (
     LaunchpadBrowserRequest,
@@ -24,6 +26,15 @@
     )
 
 
+class SpecificationsFacet:
+    implements(IFacet)
+
+    name = "specifications"
+    rootsite = "blueprints"
+    text = "Blueprints"
+    default_view = "+specs"
+
+
 class BlueprintsLayer(IBrowserRequest, IDefaultBrowserLayer):
     """The Blueprints layer."""
 

=== modified file 'lib/lp/bugs/browser/bugtarget.py'
--- lib/lp/bugs/browser/bugtarget.py	2014-06-09 22:32:44 +0000
+++ lib/lp/bugs/browser/bugtarget.py	2014-11-24 06:00:00 +0000
@@ -6,7 +6,6 @@
 __metaclass__ = type
 
 __all__ = [
-    "BugsFacetBreadcrumb",
     "BugsPatchesView",
     "BugTargetBugListingView",
     "BugTargetBugTagsView",
@@ -138,7 +137,6 @@
     )
 from lp.services.webapp.authorization import check_permission
 from lp.services.webapp.batching import BatchNavigator
-from lp.services.webapp.breadcrumb import Breadcrumb
 from lp.services.webapp.escaping import structured
 
 # A simple vocabulary for the subscribe_to_existing_bug form field.
@@ -1268,11 +1266,6 @@
         return canonical_url(self.context)
 
 
-class BugsFacetBreadcrumb(Breadcrumb):
-    rootsite = 'bugs'
-    text = 'Bugs'
-
-
 class BugsPatchesView(LaunchpadView):
     """View list of patch attachments associated with bugs."""
 

=== modified file 'lib/lp/bugs/browser/configure.zcml'
--- lib/lp/bugs/browser/configure.zcml	2014-11-16 08:31:41 +0000
+++ lib/lp/bugs/browser/configure.zcml	2014-11-24 06:00:00 +0000
@@ -265,12 +265,6 @@
         class="lp.bugs.browser.bug.DeprecatedAssignedBugsView"
         attribute="__call__"
         permission="launchpad.AnyPerson"/>
-    <adapter
-        name="bugs"
-        provides="lp.services.webapp.interfaces.IBreadcrumb"
-        for="lp.registry.interfaces.person.IPerson"
-        factory="lp.bugs.browser.bugtarget.BugsFacetBreadcrumb"
-        permission="zope.Public"/>
     <browser:page
         for="lp.registry.interfaces.person.IPerson"
         name="+team-bugs-macro"

=== modified file 'lib/lp/bugs/configure.zcml'
--- lib/lp/bugs/configure.zcml	2014-02-24 06:50:46 +0000
+++ lib/lp/bugs/configure.zcml	2014-11-24 06:00:00 +0000
@@ -23,6 +23,10 @@
       component="lp.bugs.publisher.BugsLayer"
       provides="zope.publisher.interfaces.browser.IDefaultBrowserLayer"
       name="bugs" />
+  <utility
+      component="lp.bugs.publisher.BugsFacet"
+      provides="lp.services.webapp.interfaces.IFacet"
+      name="bugs" />
 
     <lp:help-folder folder="help" name="+help-bugs" />
 
@@ -982,18 +986,6 @@
         <allow
             interface="lp.bugs.interfaces.bugnotification.IBugNotificationSet"/>
     </securedutility>
-    <adapter
-        name="bugs"
-        provides="lp.services.webapp.interfaces.IBreadcrumb"
-        for="lp.bugs.interfaces.bugtarget.IBugTarget"
-        factory="lp.bugs.browser.bugtarget.BugsFacetBreadcrumb"
-        permission="zope.Public"/>
-    <adapter
-        name="bugs"
-        provides="lp.services.webapp.interfaces.IBreadcrumb"
-        for="lp.registry.interfaces.projectgroup.IProjectGroup"
-        factory="lp.bugs.browser.bugtarget.BugsFacetBreadcrumb"
-        permission="zope.Public"/>
 
     <!-- ProcessApportBlobJobs -->
     <class class="lp.bugs.model.apportjob.ProcessApportBlobJob">

=== modified file 'lib/lp/bugs/publisher.py'
--- lib/lp/bugs/publisher.py	2011-12-24 17:49:30 +0000
+++ lib/lp/bugs/publisher.py	2014-11-24 06:00:00 +0000
@@ -18,7 +18,10 @@
     IDefaultBrowserLayer,
     )
 
-from lp.services.webapp.interfaces import ILaunchpadContainer
+from lp.services.webapp.interfaces import (
+    IFacet,
+    ILaunchpadContainer,
+    )
 from lp.services.webapp.publication import LaunchpadBrowserPublication
 from lp.services.webapp.publisher import LaunchpadContainer
 from lp.services.webapp.servers import (
@@ -27,6 +30,15 @@
     )
 
 
+class BugsFacet:
+    implements(IFacet)
+
+    name = "bugs"
+    rootsite = "bugs"
+    text = "Bugs"
+    default_view = "+bugs"
+
+
 class BugsLayer(IBrowserRequest, IDefaultBrowserLayer):
     """The Bugs layer."""
 

=== modified file 'lib/lp/code/browser/branchlisting.py'
--- lib/lp/code/browser/branchlisting.py	2014-02-25 06:38:58 +0000
+++ lib/lp/code/browser/branchlisting.py	2014-11-24 06:00:00 +0000
@@ -12,7 +12,6 @@
     'DistributionSourcePackageBranchesView',
     'DistroSeriesBranchListingView',
     'GroupedDistributionSourcePackageBranchesView',
-    'CodeFacetBreadcrumb',
     'PersonBranchesMenu',
     'PersonCodeSummaryView',
     'PersonOwnedBranchesView',
@@ -132,15 +131,9 @@
     precache_permission_for_objects,
     )
 from lp.services.webapp.batching import TableBatchNavigator
-from lp.services.webapp.breadcrumb import Breadcrumb
 from lp.services.webapp.publisher import LaunchpadView
 
 
-class CodeFacetBreadcrumb(Breadcrumb):
-    rootsite = 'code'
-    text = 'Code'
-
-
 class BranchBadges(HasBadgeBase):
     badges = "private", "bug", "blueprint", "warning", "mergeproposal"
 

=== modified file 'lib/lp/code/browser/configure.zcml'
--- lib/lp/code/browser/configure.zcml	2014-11-16 19:56:32 +0000
+++ lib/lp/code/browser/configure.zcml	2014-11-24 06:00:00 +0000
@@ -881,13 +881,6 @@
         template="../templates/active-reviews.pt"/>
 
     <adapter
-        name="branches"
-        provides="lp.services.webapp.interfaces.IBreadcrumb"
-        for="lp.code.interfaces.hasbranches.IHasBranches"
-        factory="lp.code.browser.branchlisting.CodeFacetBreadcrumb"
-        permission="zope.Public"/>
-
-    <adapter
         provides="lp.services.webapp.interfaces.IBreadcrumb"
         for="lp.code.interfaces.branch.IBranch"
         factory="lp.code.browser.branch.BranchBreadcrumb"

=== modified file 'lib/lp/code/browser/tests/test_sourcepackagerecipe.py'
--- lib/lp/code/browser/tests/test_sourcepackagerecipe.py	2014-11-17 00:13:29 +0000
+++ lib/lp/code/browser/tests/test_sourcepackagerecipe.py	2014-11-24 06:00:00 +0000
@@ -1083,11 +1083,8 @@
 
     def test_index(self):
         build = self.makeSuccessfulBuild()
-
-        # XXX wgrant 2014-11-16 wgrant: The "Code" breadcrumb shouldn't
-        # be there, but it's difficult to avoid and hopefully temporary.
         self.assertTextMatchesExpressionIgnoreWhitespace("""\
-            Master Chef Recipes Code cake_recipe
+            Master Chef Code Recipes cake_recipe
             .*
             Description Edit
             This recipe .*changes.
@@ -1440,13 +1437,11 @@
     def test_request_builds_page(self):
         """Ensure the +request-builds page is sane."""
         recipe = self.makeRecipe()
-        # XXX wgrant 2014-11-16 wgrant: The "Code" breadcrumb shouldn't
-        # be there, but it's difficult to avoid and hopefully temporary.
         pattern = dedent("""\
             Request builds for cake_recipe
             Master Chef
+            Code
             Recipes
-            Code
             cake_recipe
             Request builds for cake_recipe
             Archive:

=== modified file 'lib/lp/code/configure.zcml'
--- lib/lp/code/configure.zcml	2014-02-18 11:40:52 +0000
+++ lib/lp/code/configure.zcml	2014-11-24 06:00:00 +0000
@@ -21,6 +21,10 @@
       component="lp.code.publisher.CodeLayer"
       provides="zope.publisher.interfaces.browser.IDefaultBrowserLayer"
       name="code" />
+  <utility
+      component="lp.code.publisher.BranchesFacet"
+      provides="lp.services.webapp.interfaces.IFacet"
+      name="branches" />
 
   <!-- Branch Merge Queues -->
   <securedutility

=== modified file 'lib/lp/code/publisher.py'
--- lib/lp/code/publisher.py	2011-12-24 17:49:30 +0000
+++ lib/lp/code/publisher.py	2014-11-24 06:00:00 +0000
@@ -5,6 +5,7 @@
 
 __metaclass__ = type
 __all__ = [
+    'BranchesFacet',
     'CodeBrowserRequest',
     'CodeLayer',
     'code_request_publication_factory',
@@ -18,7 +19,10 @@
     IDefaultBrowserLayer,
     )
 
-from lp.services.webapp.interfaces import ILaunchpadContainer
+from lp.services.webapp.interfaces import (
+    IFacet,
+    ILaunchpadContainer,
+    )
 from lp.services.webapp.publication import LaunchpadBrowserPublication
 from lp.services.webapp.publisher import LaunchpadContainer
 from lp.services.webapp.servers import (
@@ -27,6 +31,15 @@
     )
 
 
+class BranchesFacet:
+    implements(IFacet)
+
+    name = "branches"
+    rootsite = "code"
+    text = "Code"
+    default_view = "+branches"
+
+
 class CodeLayer(IBrowserRequest, IDefaultBrowserLayer):
     """The Code layer."""
 

=== modified file 'lib/lp/registry/browser/distributionsourcepackage.py'
--- lib/lp/registry/browser/distributionsourcepackage.py	2014-11-17 18:36:16 +0000
+++ lib/lp/registry/browser/distributionsourcepackage.py	2014-11-24 06:00:00 +0000
@@ -69,7 +69,7 @@
     )
 from lp.services.webapp.batching import BatchNavigator
 from lp.services.webapp.breadcrumb import Breadcrumb
-from lp.services.webapp.interfaces import IBreadcrumb
+from lp.services.webapp.interfaces import IMultiFacetedBreadcrumb
 from lp.services.webapp.menu import (
     ApplicationMenu,
     enabled_with_permission,
@@ -102,7 +102,7 @@
 
 class DistributionSourcePackageBreadcrumb(Breadcrumb):
     """Builds a breadcrumb for an `IDistributionSourcePackage`."""
-    implements(IBreadcrumb, IHeadingBreadcrumb)
+    implements(IHeadingBreadcrumb, IMultiFacetedBreadcrumb)
 
     @property
     def text(self):

=== modified file 'lib/lp/registry/browser/distroseries.py'
--- lib/lp/registry/browser/distroseries.py	2014-08-01 06:12:40 +0000
+++ lib/lp/registry/browser/distroseries.py	2014-11-24 06:00:00 +0000
@@ -27,7 +27,10 @@
 from zope.component import getUtility
 from zope.event import notify
 from zope.formlib import form
-from zope.interface import Interface
+from zope.interface import (
+    implements,
+    Interface,
+    )
 from zope.lifecycleevent import ObjectCreatedEvent
 from zope.schema import (
     Choice,
@@ -90,6 +93,7 @@
 from lp.services.webapp.batching import BatchNavigator
 from lp.services.webapp.breadcrumb import Breadcrumb
 from lp.services.webapp.escaping import structured
+from lp.services.webapp.interfaces import IMultiFacetedBreadcrumb
 from lp.services.webapp.menu import (
     ApplicationMenu,
     enabled_with_permission,
@@ -198,6 +202,7 @@
 
 class DistroSeriesBreadcrumb(Breadcrumb):
     """Builds a breadcrumb for an `IDistroSeries`."""
+    implements(IMultiFacetedBreadcrumb)
 
     @property
     def text(self):

=== modified file 'lib/lp/registry/browser/person.py'
--- lib/lp/registry/browser/person.py	2014-11-17 18:36:16 +0000
+++ lib/lp/registry/browser/person.py	2014-11-24 06:00:00 +0000
@@ -252,6 +252,7 @@
 from lp.services.webapp.breadcrumb import DisplaynameBreadcrumb
 from lp.services.webapp.interfaces import (
     ILaunchBag,
+    IMultiFacetedBreadcrumb,
     IOpenLaunchBag,
     )
 from lp.services.webapp.login import (
@@ -276,7 +277,7 @@
 
 
 class PersonBreadcrumb(DisplaynameBreadcrumb):
-    implements(IHeadingBreadcrumb)
+    implements(IHeadingBreadcrumb, IMultiFacetedBreadcrumb)
 
 
 class RestrictedMembershipsPersonView(LaunchpadView):

=== modified file 'lib/lp/registry/browser/personproduct.py'
--- lib/lp/registry/browser/personproduct.py	2014-02-26 04:41:31 +0000
+++ lib/lp/registry/browser/personproduct.py	2014-11-24 06:00:00 +0000
@@ -12,6 +12,7 @@
 
 
 from zope.component import queryAdapter
+from zope.interface import implements
 from zope.traversing.interfaces import IPathAdapter
 
 from lp.app.errors import NotFoundError
@@ -19,11 +20,11 @@
 from lp.registry.interfaces.personproduct import IPersonProduct
 from lp.services.webapp import (
     canonical_url,
-    Link,
     Navigation,
     StandardLaunchpadFacets,
     )
 from lp.services.webapp.breadcrumb import Breadcrumb
+from lp.services.webapp.interfaces import IMultiFacetedBreadcrumb
 
 
 class PersonProductNavigation(Navigation):
@@ -43,6 +44,7 @@
 
 class PersonProductBreadcrumb(Breadcrumb):
     """Breadcrumb for an `IPersonProduct`."""
+    implements(IMultiFacetedBreadcrumb)
 
     @property
     def text(self):

=== modified file 'lib/lp/registry/browser/pillar.py'
--- lib/lp/registry/browser/pillar.py	2014-11-17 18:36:16 +0000
+++ lib/lp/registry/browser/pillar.py	2014-11-24 06:00:00 +0000
@@ -67,6 +67,7 @@
     Breadcrumb,
     DisplaynameBreadcrumb,
     )
+from lp.services.webapp.interfaces import IMultiFacetedBreadcrumb
 from lp.services.webapp.menu import (
     ApplicationMenu,
     enabled_with_permission,
@@ -83,7 +84,7 @@
 
 class PillarBreadcrumb(DisplaynameBreadcrumb):
     """Breadcrumb that uses the displayname or title as appropriate."""
-    implements(IHeadingBreadcrumb)
+    implements(IHeadingBreadcrumb, IMultiFacetedBreadcrumb)
 
     @property
     def detail(self):

=== modified file 'lib/lp/registry/browser/productseries.py'
--- lib/lp/registry/browser/productseries.py	2014-07-07 03:32:50 +0000
+++ lib/lp/registry/browser/productseries.py	2014-11-24 06:00:00 +0000
@@ -136,6 +136,7 @@
 from lp.services.webapp.batching import BatchNavigator
 from lp.services.webapp.breadcrumb import Breadcrumb
 from lp.services.webapp.escaping import structured
+from lp.services.webapp.interfaces import IMultiFacetedBreadcrumb
 from lp.services.worlddata.helpers import browser_languages
 from lp.services.worlddata.interfaces.country import ICountry
 from lp.services.worlddata.interfaces.language import ILanguageSet
@@ -188,6 +189,7 @@
 
 class ProductSeriesBreadcrumb(Breadcrumb):
     """Builds a breadcrumb for an `IProductSeries`."""
+    implements(IMultiFacetedBreadcrumb)
 
     @property
     def text(self):

=== modified file 'lib/lp/registry/browser/sourcepackage.py'
--- lib/lp/registry/browser/sourcepackage.py	2014-11-19 00:22:50 +0000
+++ lib/lp/registry/browser/sourcepackage.py	2014-11-24 06:00:00 +0000
@@ -94,7 +94,7 @@
     )
 from lp.services.webapp.breadcrumb import Breadcrumb
 from lp.services.webapp.escaping import structured
-from lp.services.webapp.interfaces import IBreadcrumb
+from lp.services.webapp.interfaces import IMultiFacetedBreadcrumb
 from lp.services.webapp.publisher import LaunchpadView
 from lp.services.worlddata.helpers import browser_languages
 from lp.services.worlddata.interfaces.country import ICountry
@@ -203,7 +203,7 @@
 @adapter(ISourcePackage)
 class SourcePackageBreadcrumb(Breadcrumb):
     """Builds a breadcrumb for an `ISourcePackage`."""
-    implements(IBreadcrumb)
+    implements(IMultiFacetedBreadcrumb)
 
     @property
     def text(self):

=== modified file 'lib/lp/registry/browser/team.py'
--- lib/lp/registry/browser/team.py	2014-11-17 18:36:16 +0000
+++ lib/lp/registry/browser/team.py	2014-11-24 06:00:00 +0000
@@ -173,7 +173,10 @@
     )
 from lp.services.webapp.breadcrumb import Breadcrumb
 from lp.services.webapp.escaping import structured
-from lp.services.webapp.interfaces import ILaunchBag
+from lp.services.webapp.interfaces import (
+    ILaunchBag,
+    IMultiFacetedBreadcrumb,
+    )
 
 
 class TeamPrivacyAdapter:
@@ -1247,7 +1250,7 @@
 
 class TeamBreadcrumb(Breadcrumb):
     """Builds a breadcrumb for an `ITeam`."""
-    implements(IHeadingBreadcrumb)
+    implements(IHeadingBreadcrumb, IMultiFacetedBreadcrumb)
 
     @property
     def text(self):

=== modified file 'lib/lp/registry/configure.zcml'
--- lib/lp/registry/configure.zcml	2014-11-17 18:36:16 +0000
+++ lib/lp/registry/configure.zcml	2014-11-24 06:00:00 +0000
@@ -1864,7 +1864,8 @@
         factory="lp.registry.adapters.sourcepackage_to_distribution"
         permission="zope.Public"/>
     <adapter
-        factory="lp.registry.browser.sourcepackage.SourcePackageBreadcrumb"/>
+        provides="lp.services.webapp.interfaces.IBreadcrumb"
+        factory="lp.registry.browser.sourcepackage.SourcePackageBreadcrumb" />
     <adapter
         provides="lp.registry.interfaces.sourcepackagename.ISourcePackageName"
         for="lp.registry.interfaces.sourcepackage.ISourcePackage"

=== modified file 'lib/lp/services/webapp/breadcrumb.py'
--- lib/lp/services/webapp/breadcrumb.py	2014-11-16 09:15:46 +0000
+++ lib/lp/services/webapp/breadcrumb.py	2014-11-24 06:00:00 +0000
@@ -33,8 +33,10 @@
     _detail = None
     _url = None
     inside = None
+    _rootsite = None
 
-    def __init__(self, context, url=None, text=None, inside=None):
+    def __init__(self, context, url=None, text=None, inside=None,
+                 rootsite=None):
         self.context = context
         if url is not None:
             self._url = url
@@ -42,6 +44,8 @@
             self.text = text
         if inside is not None:
             self.inside = inside
+        if rootsite is not None:
+            self._rootsite = rootsite
 
     @property
     def rootsite(self):
@@ -50,6 +54,8 @@
         If the `ICanonicalUrlData` for our context defines a rootsite, we
         return that, otherwise we return 'mainsite'.
         """
+        if self._rootsite is not None:
+            return self._rootsite
         url_data = ICanonicalUrlData(self.context)
         if url_data.rootsite:
             return url_data.rootsite

=== modified file 'lib/lp/services/webapp/interfaces.py'
--- lib/lp/services/webapp/interfaces.py	2014-11-16 09:15:46 +0000
+++ lib/lp/services/webapp/interfaces.py	2014-11-24 06:00:00 +0000
@@ -78,6 +78,14 @@
 # Menus and Facets
 #
 
+class IFacet(Interface):
+    """Interface for metadata about facets."""
+
+    rootsite = Attribute("Rootsite name")
+    title = Attribute("Title")
+    default_view = Attribute("Default view for multi-faceted objects")
+
+
 class IMenu(Interface):
     """Public interface for facets, menus, extra facets and extra menus."""
 
@@ -234,6 +242,10 @@
         'Next object up the chain. If not specified, the URL parent is used.')
 
 
+class IMultiFacetedBreadcrumb(Interface):
+    """A breadcrumb link for an object that transcends facets."""
+
+
 #
 # Canonical URLs
 #

=== modified file 'lib/lp/translations/browser/translations.py'
--- lib/lp/translations/browser/translations.py	2014-02-24 06:50:46 +0000
+++ lib/lp/translations/browser/translations.py	2014-11-24 06:00:00 +0000
@@ -10,7 +10,6 @@
     'TranslationsLanguageBreadcrumb',
     'TranslationsMixin',
     'TranslationsRedirectView',
-    'TranslationsFacetBreadcrumb',
     ]
 
 from zope.component import getUtility
@@ -164,11 +163,6 @@
             target, request, status=301)
 
 
-class TranslationsFacetBreadcrumb(Breadcrumb):
-    rootsite = 'translations'
-    text = 'Translations'
-
-
 class TranslationsLanguageBreadcrumb(Breadcrumb):
     """Breadcrumb for objects with language."""
 

=== modified file 'lib/lp/translations/configure.zcml'
--- lib/lp/translations/configure.zcml	2014-02-24 06:50:46 +0000
+++ lib/lp/translations/configure.zcml	2014-11-24 06:00:00 +0000
@@ -17,11 +17,14 @@
   <publisher
       name="translations"
       factory="lp.translations.publisher.translations_request_publication_factory"/>
-
   <utility
       component="lp.translations.publisher.TranslationsLayer"
       provides="zope.publisher.interfaces.browser.IDefaultBrowserLayer"
       name="translations" />
+  <utility
+      component="lp.translations.publisher.TranslationsFacet"
+      provides="lp.services.webapp.interfaces.IFacet"
+      name="translations" />
 
     <lp:help-folder folder="help" name="+help-translations" />
 
@@ -219,45 +222,6 @@
             interface="lp.translations.interfaces.pofiletranslator.IPOFileTranslatorSet"/>
     </securedutility>
 
-    <!-- Breadcrumbs for DistroSeries, Distribution, ProductSeries, Product,
-         ProjectGroup, TranslationsPerson -->
-    <adapter
-        name="translations"
-        provides="lp.services.webapp.interfaces.IBreadcrumb"
-        for="lp.registry.interfaces.distribution.IDistribution"
-        factory="lp.translations.browser.translations.TranslationsFacetBreadcrumb"
-        permission="zope.Public"/>
-    <adapter
-        name="translations"
-        provides="lp.services.webapp.interfaces.IBreadcrumb"
-        for="lp.registry.interfaces.distroseries.IDistroSeries"
-        factory="lp.translations.browser.translations.TranslationsFacetBreadcrumb"
-        permission="zope.Public"/>
-    <adapter
-        name="translations"
-        provides="lp.services.webapp.interfaces.IBreadcrumb"
-        for="lp.registry.interfaces.product.IProduct"
-        factory="lp.translations.browser.translations.TranslationsFacetBreadcrumb"
-        permission="zope.Public"/>
-    <adapter
-        name="translations"
-        provides="lp.services.webapp.interfaces.IBreadcrumb"
-        for="lp.registry.interfaces.productseries.IProductSeries"
-        factory="lp.translations.browser.translations.TranslationsFacetBreadcrumb"
-        permission="zope.Public"/>
-    <adapter
-        name="translations"
-        provides="lp.services.webapp.interfaces.IBreadcrumb"
-        for="lp.registry.interfaces.projectgroup.IProjectGroup"
-        factory="lp.translations.browser.translations.TranslationsFacetBreadcrumb"
-        permission="zope.Public"/>
-    <adapter
-        name="translations"
-        provides="lp.services.webapp.interfaces.IBreadcrumb"
-        for="lp.registry.interfaces.person.IPerson"
-        factory="lp.translations.browser.translations.TranslationsFacetBreadcrumb"
-        permission="zope.Public"/>
-
     <!-- Subscribers to registry objects -->
 
     <subscriber

=== modified file 'lib/lp/translations/publisher.py'
--- lib/lp/translations/publisher.py	2011-12-24 17:49:30 +0000
+++ lib/lp/translations/publisher.py	2014-11-24 06:00:00 +0000
@@ -17,6 +17,7 @@
     IDefaultBrowserLayer,
     )
 
+from lp.services.webapp.interfaces import IFacet
 from lp.services.webapp.publication import LaunchpadBrowserPublication
 from lp.services.webapp.servers import (
     LaunchpadBrowserRequest,
@@ -24,6 +25,15 @@
     )
 
 
+class TranslationsFacet:
+    implements(IFacet)
+
+    name = "translations"
+    rootsite = "translations"
+    text = "Translations"
+    default_view = "+translations"
+
+
 class TranslationsLayer(IBrowserRequest, IDefaultBrowserLayer):
     """The Translations layer."""
 

=== modified file 'lib/lp/translations/stories/standalone/xx-pofile-export.txt'
--- lib/lp/translations/stories/standalone/xx-pofile-export.txt	2010-12-28 17:16:56 +0000
+++ lib/lp/translations/stories/standalone/xx-pofile-export.txt	2014-11-24 06:00:00 +0000
@@ -19,8 +19,8 @@
     >>> user_browser.getLink('Download').click()
 
     >>> print user_browser.title
-    Download translation : Spanish (es)...
-    ...evolution... package : Translations : Hoary (5.04) : Ubuntu
+    Download translation : Spanish (es)... : Translations :
+    ...evolution... package : Hoary (5.04) : Ubuntu
 
     >>> print find_main_content(user_browser.contents)
     <...

=== modified file 'lib/lp/translations/stories/standalone/xx-pofile-translate-message-filtering.txt'
--- lib/lp/translations/stories/standalone/xx-pofile-translate-message-filtering.txt	2011-12-24 15:18:32 +0000
+++ lib/lp/translations/stories/standalone/xx-pofile-translate-message-filtering.txt	2014-11-24 06:00:00 +0000
@@ -48,8 +48,8 @@
     ...     'http://translations.launchpad.dev/ubuntu/hoary/'
     ...     '+source/evolution/+pots/evolution-2.2/es/+translate')
     >>> print user_browser.title
-    Spanish (es) : Template ...evolution-2.2... : ...evolution... package :
-    Translations : Hoary (5.04) : Ubuntu
+    Spanish (es) : Template ...evolution-2.2... : Translations :
+    ...evolution... package : Hoary (5.04) : Ubuntu
 
 He can see that there are 22 messages.
 
@@ -126,7 +126,7 @@
     >>> user_browser.getControl('Change').click()
     >>> print user_browser.title
     English (Australia) (en_AU) : Template ...evolution-2.2... :
-    ...evolution... package : Translations : Hoary (5.04) : Ubuntu
+    Translations : ...evolution... package : Hoary (5.04) : Ubuntu
 
     >>> contents = find_main_content(user_browser.contents)
     >>> print_batch_header(contents)

=== modified file 'lib/lp/translations/stories/standalone/xx-potemplate-index.txt'
--- lib/lp/translations/stories/standalone/xx-potemplate-index.txt	2012-08-06 06:09:19 +0000
+++ lib/lp/translations/stories/standalone/xx-potemplate-index.txt	2014-11-24 06:00:00 +0000
@@ -12,8 +12,8 @@
     >>> anon_browser.open("http://translations.launchpad.dev/";
     ...     "ubuntu/hoary/+source/evolution/+pots/evolution-2.2/")
     >>> print anon_browser.title
-    Template ...evolution-2.2... :
-    ...evolution... package : Translations : Hoary (5.04) : Ubuntu
+    Template ...evolution-2.2... : Translations :
+    ...evolution... package : Hoary (5.04) : Ubuntu
 
 The owner of the template is diplayed.
 

=== modified file 'lib/lp/translations/stories/standalone/xx-rosetta-sourcepackage-list.txt'
--- lib/lp/translations/stories/standalone/xx-rosetta-sourcepackage-list.txt	2009-11-09 17:08:21 +0000
+++ lib/lp/translations/stories/standalone/xx-rosetta-sourcepackage-list.txt	2014-11-24 06:00:00 +0000
@@ -8,7 +8,7 @@
     ...     'http://translations.launchpad.dev/ubuntu/hoary/'
     ...     '+source/evolution')
     >>> anon_browser.title
-    '...evolution...package : Translations : Hoary (5.04) : Ubuntu'
+    'Translations : ...evolution...package : Hoary (5.04) : Ubuntu'
 
     >>> content = find_main_content(anon_browser.contents)
     >>> print extract_text(content.findAll('h1')[0]).encode(

=== modified file 'lib/lp/translations/stories/standalone/xx-sourcepackage-export.txt'
--- lib/lp/translations/stories/standalone/xx-sourcepackage-export.txt	2013-09-27 04:13:23 +0000
+++ lib/lp/translations/stories/standalone/xx-sourcepackage-export.txt	2014-11-24 06:00:00 +0000
@@ -166,7 +166,7 @@
     ...     'http://translations.launchpad.dev/'
     ...     'ubuntu/hoary/+source/mozilla/+export')
     >>> browser.title
-    'Download : \xe2\x80\x9cmozilla...
+    'Download : Translations : \xe2\x80\x9cmozilla...
 
     >>> browser.getControl('Request Download').click()
 


References