← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~wgrant/launchpad/unify-person-branches into lp:launchpad

 

William Grant has proposed merging lp:~wgrant/launchpad/unify-person-branches into lp:launchpad.

Commit message:
Merge Person(Product):+{,owned,subscribed,registered}branches. +branches now has an extra filter dropdown.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~wgrant/launchpad/unify-person-branches/+merge/243893

Merge Person(Product):+{,owned,subscribed,registered}branches. +branches now has an extra filter dropdown.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~wgrant/launchpad/unify-person-branches into lp:launchpad.
=== modified file 'lib/lp/code/browser/branch.py'
--- lib/lp/code/browser/branch.py	2014-11-27 20:52:37 +0000
+++ lib/lp/code/browser/branch.py	2014-12-06 12:47:57 +0000
@@ -20,19 +20,14 @@
     'BranchNameValidationMixin',
     'BranchNavigation',
     'BranchEditMenu',
-    'BranchInProductView',
     'BranchUpgradeView',
     'BranchURL',
     'BranchView',
-    'BranchSubscriptionsView',
     'RegisterBranchMergeProposalView',
     'TryImportAgainView',
     ]
 
-from datetime import (
-    datetime,
-    timedelta,
-    )
+from datetime import datetime
 
 from lazr.enum import (
     EnumeratedType,
@@ -443,15 +438,6 @@
             return False
         return self.context.hasSubscription(self.user)
 
-    def recent_revision_count(self, days=30):
-        """Number of revisions committed during the last N days."""
-        timestamp = datetime.now(pytz.UTC) - timedelta(days=days)
-        return self.context.getRevisionsSince(timestamp).count()
-
-    def owner_is_registrant(self):
-        """Is the branch owner the registrant?"""
-        return self.context.owner == self.context.registrant
-
     def owner_is_reviewer(self):
         """Is the branch owner the default reviewer?"""
         if self.context.reviewer == None:
@@ -669,12 +655,6 @@
         return self.context.getSpecificationLinks(self.user)
 
 
-class BranchInProductView(BranchView):
-
-    show_person_link = True
-    show_product_link = False
-
-
 class BranchNameValidationMixin:
     """Provide name validation logic used by several branch view classes."""
 
@@ -1191,19 +1171,6 @@
         return {'reviewer': self.context.code_reviewer}
 
 
-class BranchSubscriptionsView(LaunchpadView):
-    """The view for the branch subscriptions portlet.
-
-    The view is used to provide a decorated list of branch subscriptions
-    in order to provide links to be able to edit the subscriptions
-    based on whether or not the user is able to edit the subscription.
-    """
-
-    def owner_is_registrant(self):
-        """Return whether or not owner is the same as the registrant"""
-        return self.context.owner == self.context.registrant
-
-
 class BranchMergeQueueView(LaunchpadView):
     """The view used to render the merge queue for a branch."""
 
@@ -1457,3 +1424,4 @@
     @property
     def prefix(self):
         return "tryagain"
+

=== modified file 'lib/lp/code/browser/branchlisting.py'
--- lib/lp/code/browser/branchlisting.py	2014-11-24 01:30:25 +0000
+++ lib/lp/code/browser/branchlisting.py	2014-12-06 12:47:57 +0000
@@ -13,10 +13,8 @@
     'DistroSeriesBranchListingView',
     'GroupedDistributionSourcePackageBranchesView',
     'PersonBranchesMenu',
+    'PersonBranchesView',
     'PersonCodeSummaryView',
-    'PersonOwnedBranchesView',
-    'PersonRegisteredBranchesView',
-    'PersonSubscribedBranchesView',
     'PersonTeamBranchesView',
     'ProductBranchListingView',
     'ProductBranchesMenu',
@@ -282,6 +280,28 @@
         """)
 
 
+class PersonBranchCategory(EnumeratedType):
+    """Choices for filtering lists of branches related to people."""
+
+    OWNED = Item("""
+        Owned
+
+        Show branches owned by the person or team.
+        """)
+
+    SUBSCRIBED = Item("""
+        Subscribed
+
+        Show branches subscribed to by the person or team.
+        """)
+
+    REGISTERED = Item("""
+        Registered
+
+        Show branches registered by the person or team.
+        """)
+
+
 class IBranchListingFilter(Interface):
     """The schema for the branch listing filtering/ordering form."""
 
@@ -303,6 +323,14 @@
         default=BranchListingSort.LIFECYCLE)
 
 
+class IPersonBranchListingFilter(IBranchListingFilter):
+    """The schema for the branch listing filtering/ordering form for people."""
+
+    category = Choice(
+        title=_('Category'), vocabulary=PersonBranchCategory,
+        default=PersonBranchCategory.OWNED)
+
+
 class BranchListingItemsMixin:
     """Mixin class to create BranchListingItems."""
 
@@ -842,8 +870,8 @@
 
     usedfor = IPerson
     facet = 'branches'
-    links = ['registered', 'owned', 'subscribed',
-             'active_reviews', 'mergequeues', 'source_package_recipes']
+    links = [
+        'owned', 'active_reviews', 'mergequeues', 'source_package_recipes']
     extra_attributes = ['mergequeue_count']
 
     @property
@@ -855,17 +883,10 @@
         """
         return self.context
 
+    # XXX: Rename.
     def owned(self):
         return Link(
-            canonical_url(self.context, rootsite='code'), 'Owned branches')
-
-    def registered(self):
-        enabled = not self.person.is_team
-        return Link(
-            '+registeredbranches', 'Registered branches', enabled=enabled)
-
-    def subscribed(self):
-        return Link('+subscribedbranches', 'Subscribed branches')
+            canonical_url(self.context, rootsite='code'), 'Branches')
 
     def active_reviews(self):
         return Link('+activereviews', 'Active reviews')
@@ -879,8 +900,7 @@
 class PersonProductBranchesMenu(PersonBranchesMenu):
 
     usedfor = IPersonProduct
-    links = ['registered', 'owned', 'subscribed', 'active_reviews',
-             'source_package_recipes']
+    links = ['owned', 'active_reviews', 'source_package_recipes']
 
     @property
     def person(self):
@@ -892,6 +912,10 @@
     """Base class used for different person listing views."""
 
     @property
+    def person(self):
+        return self.context
+
+    @property
     def show_action_menu(self):
         if self.user is not None:
             return self.user.inTeam(self.context)
@@ -907,6 +931,28 @@
         values['sort_by'] = BranchListingSort.MOST_RECENTLY_CHANGED_FIRST
         return values
 
+    def _getCollection(self):
+        category = PersonBranchCategory.OWNED
+        if self.widgets['category'].hasValidInput():
+            category = self.widgets['category'].getInputValue()
+        if category == PersonBranchCategory.OWNED:
+            return getUtility(IAllBranches).ownedBy(self.person)
+        elif category == PersonBranchCategory.SUBSCRIBED:
+            return getUtility(IAllBranches).subscribedBy(self.person)
+        elif category == PersonBranchCategory.REGISTERED:
+            return getUtility(IAllBranches).registeredBy(self.person)
+
+
+class PersonBranchesView(PersonBaseBranchListingView):
+    """View for branch listing for a person's branches."""
+
+    schema = IPersonBranchListingFilter
+    field_names = ['category', 'lifecycle', 'sort_by']
+    custom_widget('category', LaunchpadDropdownWidget)
+
+    page_title = label = None
+    no_sort_by = (BranchListingSort.DEFAULT, BranchListingSort.OWNER)
+
     @property
     def no_branch_message(self):
         if (self.selected_lifecycle_status is not None
@@ -922,37 +968,44 @@
         return message % self.context.displayname
 
 
-class PersonRegisteredBranchesView(PersonBaseBranchListingView):
-    """View for branch listing for a person's registered branches."""
-
-    page_title = _('Registered')
-    label_template = 'Bazaar branches registered by %(displayname)s'
-    no_sort_by = (BranchListingSort.DEFAULT, BranchListingSort.OWNER)
-
-    def _getCollection(self):
-        return getUtility(IAllBranches).registeredBy(self.context)
-
-
-class PersonOwnedBranchesView(PersonBaseBranchListingView):
-    """View for branch listing for a person's owned branches."""
-
-    page_title = _('Owned')
-    label_template = 'Bazaar branches owned by %(displayname)s'
-    no_sort_by = (BranchListingSort.DEFAULT, BranchListingSort.OWNER)
-
-    def _getCollection(self):
-        return getUtility(IAllBranches).ownedBy(self.context)
-
-
-class PersonSubscribedBranchesView(PersonBaseBranchListingView):
-    """View for branch listing for a person's subscribed branches."""
-
-    page_title = _('Subscribed')
-    label_template = 'Bazaar branches subscribed to by %(displayname)s'
-    no_sort_by = (BranchListingSort.DEFAULT, )
-
-    def _getCollection(self):
-        return getUtility(IAllBranches).subscribedBy(self.context)
+class PersonProductBranchesView(PersonBranchesView):
+    """Branch listing for a person's branches of a product."""
+
+    label_template = 'Branches of %(product)s for %(person)s'
+    no_sort_by = (
+        BranchListingSort.DEFAULT, BranchListingSort.OWNER,
+        BranchListingSort.PRODUCT)
+    show_action_menu = False
+
+    @property
+    def person(self):
+        """Return the person from the PersonProduct context."""
+        return self.context.person
+
+    @property
+    def label(self):
+        return self.label_template % {
+            'person': self.context.person.displayname,
+            'product': self.context.product.displayname}
+
+    @property
+    def no_branch_message(self):
+        """Provide a more appropriate message for no branches."""
+        if (self.selected_lifecycle_status is not None
+            and self.hasAnyBranchesVisibleByUser()):
+            message = (
+                'There are branches of %s for %s but none of them '
+                'match the current filter criteria for this page. '
+                'Try filtering on "Any Status".')
+        else:
+            message = (
+                'There are no branches of %s for %s in Launchpad today.')
+        return message % (
+            self.context.product.displayname, self.context.person.displayname)
+
+    def _getCollection(self):
+        coll = super(PersonProductBranchesView, self)._getCollection()
+        return coll.inProduct(self.context.product)
 
 
 class PersonTeamBranchesView(LaunchpadView):
@@ -1575,72 +1628,3 @@
                 num_branches='%s %s' % (num_branches, num_branches_text),
                 dev_focus_css=dev_focus_css,
                 linked=(series != our_series))
-
-
-class PersonProductBaseBranchesView(PersonBaseBranchListingView):
-    """A base view used for other person-product branch listings."""
-
-    no_sort_by = (BranchListingSort.DEFAULT, BranchListingSort.PRODUCT)
-    show_action_menu = False
-
-    @property
-    def person(self):
-        """Return the person from the PersonProduct context."""
-        return self.context.person
-
-    @property
-    def label(self):
-        return self.label_template % {
-            'person': self.context.person.displayname,
-            'product': self.context.product.displayname}
-
-    @property
-    def no_branch_message(self):
-        """Provide a more appropriate message for no branches."""
-        if (self.selected_lifecycle_status is not None
-            and self.hasAnyBranchesVisibleByUser()):
-            message = (
-                'There are branches of %s owned by %s but none of them '
-                'match the current filter criteria for this page. '
-                'Try filtering on "Any Status".')
-        else:
-            message = (
-                'There are no branches of %s owned by %s in Launchpad today.')
-        return message % (
-            self.context.product.displayname, self.context.person.displayname)
-
-
-class PersonProductOwnedBranchesView(PersonProductBaseBranchesView):
-    """Branch listing for a person's owned branches of a product."""
-
-    no_sort_by = (BranchListingSort.DEFAULT,
-                  BranchListingSort.OWNER,
-                  BranchListingSort.PRODUCT)
-
-    label_template = 'Bazaar Branches of %(product)s owned by %(person)s'
-
-    def _getCollection(self):
-        return getUtility(IAllBranches).ownedBy(
-            self.context.person).inProduct(self.context.product)
-
-
-class PersonProductRegisteredBranchesView(PersonProductBaseBranchesView):
-    """Branch listing for a person's registered branches of a product."""
-
-    label_template = (
-        'Bazaar Branches of %(product)s registered by %(person)s')
-
-    def _getCollection(self):
-        return getUtility(IAllBranches).registeredBy(
-            self.context.person).inProduct(self.context.product)
-
-
-class PersonProductSubscribedBranchesView(PersonProductBaseBranchesView):
-    """Branch listing for a person's subscribed branches of a product."""
-
-    label_template = (
-        'Bazaar Branches of %(product)s subscribed to by %(person)s')
-
-    def _getCollection(self):
-        return getUtility(IAllBranches).subscribedBy(
-            self.context.person).inProduct(self.context.product)

=== modified file 'lib/lp/code/browser/configure.zcml'
--- lib/lp/code/browser/configure.zcml	2014-11-24 06:20:03 +0000
+++ lib/lp/code/browser/configure.zcml	2014-12-06 12:47:57 +0000
@@ -362,7 +362,6 @@
     <browser:page
         name="+portlet-subscribers"
         for="lp.code.interfaces.branch.IBranch"
-        class="lp.code.browser.branch.BranchSubscriptionsView"
         permission="zope.Public"
         template="../templates/branch-portlet-subscribers.pt"/>
     <browser:page
@@ -371,15 +370,6 @@
         class="lp.code.browser.branchsubscription.BranchPortletSubscribersContent"
         template="../templates/branch-portlet-subscribers-content.pt"
         permission="zope.Public" />
-    <browser:pages
-        for="lp.code.interfaces.branch.IBranch"
-        facet="overview"
-        permission="zope.Public"
-        class="lp.code.browser.branch.BranchInProductView">
-        <browser:page
-            name="+product-summary-listing"
-            template="../templates/branch-summary-listing.pt"/>
-    </browser:pages>
     <browser:page
         for="lp.code.interfaces.branch.IBranch"
         name="+macros"
@@ -700,28 +690,26 @@
             name="+branches"/>
         <browser:page
             for="lp.registry.interfaces.person.IPerson"
-            class="lp.code.browser.branchlisting.PersonOwnedBranchesView"
+            class="lp.code.browser.branchlisting.PersonBranchesView"
             permission="zope.Public"
             name="+branches"
             template="../templates/person-branches.pt"/>
-        <browser:page
+        <browser:renamed-page
             for="lp.registry.interfaces.person.IPerson"
-            class="lp.code.browser.branchlisting.PersonOwnedBranchesView"
-            permission="zope.Public"
             name="+ownedbranches"
-            template="../templates/person-branches.pt"/>
-        <browser:page
-            for="lp.registry.interfaces.person.IPerson"
-            class="lp.code.browser.branchlisting.PersonSubscribedBranchesView"
-            permission="zope.Public"
+            new_name="+branches"
+            rootsite="code"/>
+        <browser:renamed-page
+            for="lp.registry.interfaces.person.IPerson"
+            name="+registeredbranches"
+            new_name="+branches"
+            rootsite="code"/>
+        <browser:renamed-page
+            for="lp.registry.interfaces.person.IPerson"
             name="+subscribedbranches"
-            template="../templates/person-branches.pt"/>
-        <browser:page
-            for="lp.registry.interfaces.person.IPerson"
-            class="lp.code.browser.branchlisting.PersonRegisteredBranchesView"
-            permission="zope.Public"
-            name="+registeredbranches"
-            template="../templates/person-branches.pt"/>
+            new_name="+branches"
+            rootsite="code"/>
+        <!-- xXX: Need redirects from +{owned,registered,subscribed}branches. -->
         <browser:page
             for="lp.registry.interfaces.person.IPerson"
             class="lp.code.browser.branchlisting.PersonCodeSummaryView"
@@ -824,25 +812,26 @@
 
     <browser:page
         for="lp.registry.interfaces.personproduct.IPersonProduct"
-        class="lp.code.browser.branchlisting.PersonProductOwnedBranchesView"
+        class="lp.code.browser.branchlisting.PersonProductBranchesView"
         permission="zope.Public"
         name="+branches"
         template="../templates/person-branches.pt"
         />
-    <browser:page
-        for="lp.registry.interfaces.personproduct.IPersonProduct"
-        class="lp.code.browser.branchlisting.PersonProductSubscribedBranchesView"
-        permission="zope.Public"
+    <browser:renamed-page
+        for="lp.registry.interfaces.personproduct.IPersonProduct"
+        name="+ownedbranches"
+        new_name="+branches"
+        rootsite="code"/>
+    <browser:renamed-page
+        for="lp.registry.interfaces.personproduct.IPersonProduct"
+        name="+registeredbranches"
+        new_name="+branches"
+        rootsite="code"/>
+    <browser:renamed-page
+        for="lp.registry.interfaces.personproduct.IPersonProduct"
         name="+subscribedbranches"
-        template="../templates/person-branches.pt"
-        />
-    <browser:page
-        for="lp.registry.interfaces.personproduct.IPersonProduct"
-        class="lp.code.browser.branchlisting.PersonProductRegisteredBranchesView"
-        permission="zope.Public"
-        name="+registeredbranches"
-        template="../templates/person-branches.pt"
-        />
+        new_name="+branches"
+        rootsite="code"/>
     <browser:page
         for="lp.registry.interfaces.personproduct.IPersonProduct"
         class="lp.code.browser.branchmergeproposallisting.PersonProductActiveReviewsView"

=== modified file 'lib/lp/code/browser/tests/test_branch.py'
--- lib/lp/code/browser/tests/test_branch.py	2014-11-16 19:56:32 +0000
+++ lib/lp/code/browser/tests/test_branch.py	2014-12-06 12:47:57 +0000
@@ -816,7 +816,7 @@
         login_person(product.owner)
         product.development_focus.branch = branch
         view = create_initialized_view(
-            branch.owner, '+ownedbranches', rootsite='code')
+            branch.owner, '+branches', rootsite='code')
         navigator = view.branches()
         [decorated_branch] = navigator.branches
         self.assertEqual("lp://dev/fooix", decorated_branch.bzr_identity)

=== modified file 'lib/lp/code/browser/tests/test_branchlisting.py'
--- lib/lp/code/browser/tests/test_branchlisting.py	2014-02-25 06:42:01 +0000
+++ lib/lp/code/browser/tests/test_branchlisting.py	2014-12-06 12:47:57 +0000
@@ -26,7 +26,6 @@
     BranchListingSort,
     BranchListingView,
     GroupedDistributionSourcePackageBranchesView,
-    PersonProductSubscribedBranchesView,
     SourcePackageBranchesView,
     )
 from lp.code.model.branch import Branch
@@ -331,10 +330,6 @@
 
         self.code_base_url = 'http://code.launchpad.dev/~barney'
         self.base_url = 'http://launchpad.dev/~barney'
-        self.registered_branches_matcher = soupmatchers.HTMLContains(
-            soupmatchers.Tag(
-                'Registered link', 'a', text='Registered branches',
-                attrs={'href': self.base_url + '/+registeredbranches'}))
         self.default_target = self.person
 
     def makeABranch(self):
@@ -347,45 +342,23 @@
             return create_initialized_view(
                 target, page_name, rootsite='code', principal=self.user)()
 
-    def test_branch_list_h1(self):
-        self.makeABranch()
-        page = self.get_branch_list_page()
-        h1_matcher = soupmatchers.HTMLContains(
-            soupmatchers.Tag(
-                'Title', 'h1', text='Bazaar branches owned by Barney'))
-        self.assertThat(page, h1_matcher)
-
     def test_branch_list_empty(self):
         page = self.get_branch_list_page()
         empty_message_matcher = soupmatchers.HTMLContains(
             soupmatchers.Tag(
-                'Empty message', 'p',
+                'Empty message', 'div',
                 text='There are no branches related to Barney '
                      'in Launchpad today.'))
         self.assertThat(page, empty_message_matcher)
 
-    def test_branch_list_registered_link(self):
-        self.makeABranch()
-        page = self.get_branch_list_page()
-        self.assertThat(page, self.registered_branches_matcher)
-
-    def test_branch_list_owned_link(self):
+    def test_branch_list_branches_link(self):
         # The link to the owned branches is always displayed.
-        owned_branches_matcher = soupmatchers.HTMLContains(
+        branches_matcher = soupmatchers.HTMLContains(
             soupmatchers.Tag(
-                'Owned link', 'a', text='Owned branches',
+                'Branches link', 'a', text='Branches',
                 attrs={'href': self.code_base_url}))
-        page = self.get_branch_list_page(page_name='+subscribedbranches')
-        self.assertThat(page, owned_branches_matcher)
-
-    def test_branch_list_subscribed_link(self):
-        # The link to the subscribed branches is always displayed.
-        subscribed_branches_matcher = soupmatchers.HTMLContains(
-            soupmatchers.Tag(
-                'Subscribed link', 'a', text='Subscribed branches',
-                attrs={'href': self.base_url + '/+subscribedbranches'}))
-        page = self.get_branch_list_page()
-        self.assertThat(page, subscribed_branches_matcher)
+        page = self.get_branch_list_page(page_name='+branches')
+        self.assertThat(page, branches_matcher)
 
     def test_branch_list_activereviews_link(self):
         # The link to the active reviews is always displayed.
@@ -396,11 +369,6 @@
         page = self.get_branch_list_page()
         self.assertThat(page, active_review_matcher)
 
-    def test_branch_list_no_registered_link_team(self):
-        self.makeABranch()
-        page = self.get_branch_list_page(target=self.team)
-        self.assertThat(page, Not(self.registered_branches_matcher))
-
     def test_branch_list_recipes_link(self):
         # The link to the source package recipes is always displayed.
         page = self.get_branch_list_page()
@@ -426,10 +394,6 @@
             self.team, self.product)
         self.code_base_url = 'http://code.launchpad.dev/~barney/bambam'
         self.base_url = 'http://launchpad.dev/~barney/bambam'
-        self.registered_branches_matcher = soupmatchers.HTMLContains(
-            soupmatchers.Tag(
-                'Registered link', 'a', text='Registered branches',
-                attrs={'href': self.base_url + '/+registeredbranches'}))
         self.default_target = self.person_product
 
     def makeABranch(self):
@@ -442,15 +406,15 @@
         h1_matcher = soupmatchers.HTMLContains(
             soupmatchers.Tag(
                 'Title', 'h1',
-                text='Bazaar Branches of Bambam owned by Barney'))
+                text='Branches of Bambam for Barney'))
         self.assertThat(page, h1_matcher)
 
     def test_branch_list_empty(self):
         page = self.get_branch_list_page()
         empty_message_matcher = soupmatchers.HTMLContains(
             soupmatchers.Tag(
-                'Empty message', 'p',
-                text='There are no branches of Bambam owned by Barney '
+                'Empty message', 'div',
+                text='There are no branches of Bambam for Barney '
                      'in Launchpad today.'))
         self.assertThat(page, empty_message_matcher)
 
@@ -798,29 +762,3 @@
         # The correct template is used for batch requests.
         product = self.factory.makeProduct(project=self.project)
         self._test_batch_template(product)
-
-
-class FauxPageTitleContext:
-
-    displayname = 'DISPLAY-NAME'
-
-    class person:
-        displayname = 'PERSON'
-
-    class product:
-        displayname = 'PRODUCT'
-
-
-class TestPageTitle(TestCase):
-    """The various views should have a page_title attribute/property."""
-
-    def test_branch_listing_view(self):
-        view = BranchListingView(FauxPageTitleContext, None)
-        self.assertEqual(
-            'Bazaar branches for DISPLAY-NAME', view.page_title)
-
-    def test_person_product_subscribed_branches_view(self):
-        view = PersonProductSubscribedBranchesView(FauxPageTitleContext, None)
-        self.assertEqual(
-            'Bazaar Branches of PRODUCT subscribed to by PERSON',
-            view.page_title)

=== modified file 'lib/lp/code/javascript/util.js'
--- lib/lp/code/javascript/util.js	2014-07-24 08:02:19 +0000
+++ lib/lp/code/javascript/util.js	2014-12-06 12:47:57 +0000
@@ -41,6 +41,10 @@
     if (Y.Lang.isValue(sortby)) {
         sortby.on('change', submit_filter);
     }
+    var category = Y.one("[id='field.category']");
+    if (Y.Lang.isValue(category)) {
+        category.on('change', submit_filter);
+    }
     Y.one('#filter_form_submit').addClass('hidden');
 };
 

=== modified file 'lib/lp/code/stories/branches/xx-branch-listings.txt'
--- lib/lp/code/stories/branches/xx-branch-listings.txt	2014-02-25 06:38:58 +0000
+++ lib/lp/code/stories/branches/xx-branch-listings.txt	2014-12-06 12:47:57 +0000
@@ -65,7 +65,9 @@
 needed, then the table is sortable and no batching navigation links are shown.
 
     >>> browser.open('http://code.launchpad.dev/~name12')
-    >>> browser.getLink('Subscribed').click()
+    >>> browser.getControl(name='field.category').displayValue = [
+    ...     'Subscribed']
+    >>> browser.getControl('Filter').click()
     >>> links = find_tag_by_id(browser.contents, 'branch-batch-links')
     >>> links is None
     True
@@ -178,11 +180,13 @@
     >>> print message.renderContents()
     There are branches related to Launchpad Developers...
 
-Subscribed branch listings shouldn't show an option for sorting by "most
+Personal branch listings shouldn't show an option for sorting by "most
 interesting" and should default to sort the most recently changed branches
 first.
 
-    >>> browser.open('http://code.launchpad.dev/~name12/+subscribedbranches')
+    >>> browser.open(
+    ...     'http://code.launchpad.dev/~name12/+branches?'
+    ...     'field.category=subscribed')
     >>> table = find_tag_by_id(browser.contents, 'branchtable')
     >>> for row in table.tbody.fetch('tr'):
     ...     print extract_text(row)
@@ -197,8 +201,8 @@
     ['most recently changed first']
     >>> filter_control.displayOptions
     ['by project name', 'by status', 'by branch name',
-    'by owner name', 'most recently changed first',
-    'most neglected first', 'newest first', 'oldest first']
+    'most recently changed first', 'most neglected first', 'newest first',
+    'oldest first']
 
 
 

=== modified file 'lib/lp/code/stories/branches/xx-branchmergeproposal-listings.txt'
--- lib/lp/code/stories/branches/xx-branchmergeproposal-listings.txt	2013-05-08 00:56:03 +0000
+++ lib/lp/code/stories/branches/xx-branchmergeproposal-listings.txt	2014-12-06 12:47:57 +0000
@@ -93,9 +93,7 @@
 
     >>> browser.open('http://code.launchpad.dev/~albert')
     >>> print_tag_with_id(browser.contents, 'portlet-person-codesummary')
-    Owned branches
-    Registered branches
-    Subscribed branches
+    Branches
     Active reviews
     Source package recipes
 

=== modified file 'lib/lp/code/stories/branches/xx-nearby-branches.txt'
--- lib/lp/code/stories/branches/xx-nearby-branches.txt	2010-01-13 22:18:14 +0000
+++ lib/lp/code/stories/branches/xx-nearby-branches.txt	2014-12-06 12:47:57 +0000
@@ -22,4 +22,3 @@
     ...     print anchor['href'], anchor.string
     /fooix                      Other Fooix branches
     /~vikings                   Other branches owned by Vikings
-    /~eric/+registeredbranches  Other branches registered by Eric

=== modified file 'lib/lp/code/stories/branches/xx-person-branches.txt'
--- lib/lp/code/stories/branches/xx-person-branches.txt	2012-10-04 01:09:26 +0000
+++ lib/lp/code/stories/branches/xx-person-branches.txt	2014-12-06 12:47:57 +0000
@@ -10,12 +10,6 @@
     >>> print browser.title
     Code : Sample Person
 
-The heading in the main content is the same as the page title text.
-
-    >>> main = find_main_content(browser.contents)
-    >>> print extract_text(main.h1)
-    Bazaar branches owned by Sample Person
-
 
 Default view for a person on code root site
 -------------------------------------------
@@ -61,12 +55,14 @@
 Registered Branches
 -------------------
 
-There is also a link which points to the registered branches page.
+There is also a filter for registered branches.
 
     >>> browser.open('http://code.launchpad.dev/~name12')
-    >>> browser.getLink('Registered').click()
+    >>> browser.getControl(name='field.category').displayValue = [
+    ...     'Registered']
+    >>> browser.getControl('Filter').click()
     >>> print browser.title
-    Registered : Code : Sample Person
+    Code : Sample Person
     >>> table = find_tag_by_id(browser.contents, 'branchtable')
     >>> for row in table.tbody.fetch('tr'):
     ...     print extract_text(row)
@@ -77,13 +73,15 @@
 Subscribed branches
 -------------------
 
-From the persons main listing page, there is also a link to subscribed
-branches.
+From the persons main listing page, there is also a filter for
+subscribed branches.
 
     >>> browser.open('http://code.launchpad.dev/~name12')
-    >>> browser.getLink('Subscribed').click()
+    >>> browser.getControl(name='field.category').displayValue = [
+    ...     'Subscribed']
+    >>> browser.getControl('Filter').click()
     >>> print browser.title
-    Subscribed : Code : Sample Person
+    Code : Sample Person
     >>> table = find_tag_by_id(browser.contents, 'branchtable')
     >>> for row in table.tbody.fetch('tr'):
     ...     print extract_text(row)
@@ -112,9 +110,7 @@
     >>> eric_browser = setupBrowser(auth="Basic eric@xxxxxxxxxxx:test")
     >>> eric_browser.open('http://code.launchpad.dev/~eric')
     >>> print_tag_with_id(eric_browser.contents, 'portlet-person-codesummary')
-    Owned branches
-    Registered branches
-    Subscribed branches
+    Branches
     Active reviews
     Source package recipes
 
@@ -128,26 +124,6 @@
     >>> eric_browser.open('http://code.launchpad.dev/~eric')
     >>> print_tag_with_id(
     ...     eric_browser.contents, 'portlet-person-codesummary')
-    Owned branches
-    Registered branches
-    Subscribed branches
-    Active reviews
-    Source package recipes
-
-Teams do not show registered branches
--------------------------------------
-
-For a team, showing registered branches does not make sense, so that
-line is omitted.
-
-    >>> browser.open('http://code.launchpad.dev/~landscape-developers')
-    >>> print_tag_with_id(
-    ...     browser.contents, 'portlet-person-codesummary')
-    Owned branches
-    Subscribed branches
-    Active reviews
-    Source package recipes
-
-    >>> browser.getLink('registered').click()
-    Traceback (most recent call last):
-    LinkNotFoundError
+    Branches
+    Active reviews
+    Source package recipes

=== modified file 'lib/lp/code/stories/branches/xx-personproduct-branch-listings.txt'
--- lib/lp/code/stories/branches/xx-personproduct-branch-listings.txt	2012-10-04 01:09:26 +0000
+++ lib/lp/code/stories/branches/xx-personproduct-branch-listings.txt	2014-12-06 12:47:57 +0000
@@ -14,7 +14,7 @@
 
     >>> browser.open('http://code.launchpad.dev/~eric/fooix')
     >>> print_tag_with_id(browser.contents, 'no-branch-message')
-    There are no branches of Fooix owned by Eric in Launchpad today.
+    There are no branches of Fooix for Eric in Launchpad today.
 
 If we create a couple of fooix branches, and a few other branches, we can see
 that only the fooix branches are shown.
@@ -39,7 +39,7 @@
 
     >>> browser.open('http://code.launchpad.dev/~eric/fooix')
     >>> print_tag_with_id(browser.contents, 'portlet-person-codesummary')
-    Owned branches ...
+    Branches ...
     >>> print_tag_with_id(browser.contents, 'branchtable')
     Name                         ...
     lp://dev/~eric/fooix/feature ...

=== modified file 'lib/lp/code/stories/branches/xx-private-branch-listings.txt'
--- lib/lp/code/stories/branches/xx-private-branch-listings.txt	2014-02-25 06:38:58 +0000
+++ lib/lp/code/stories/branches/xx-private-branch-listings.txt	2014-12-06 12:47:57 +0000
@@ -137,10 +137,15 @@
 The person code listings is the other obvious place to filter out the
 viewable branches.
 
-    >>> def print_person_code_listing(browser, url=''):
+    >>> import urllib
+    >>> def print_person_code_listing(browser, category=None):
+    ...     params = {'batch': '15'}
+    ...     if category is not None:
+    ...         params['field.category'] = category
     ...     # The batch argument is given to override the default batch
     ...     # size of five.
-    ...     full_url = 'http://code.launchpad.dev/~name12%s?batch=15' % url
+    ...     full_url = 'http://code.launchpad.dev/~name12?%s' % (
+    ...         urllib.urlencode(params),)
     ...     browser.open(full_url)
     ...     table = find_tag_by_id(browser.contents, 'branchtable')
     ...     branches = []
@@ -158,31 +163,30 @@
     >>> print_person_code_listing(anon_browser)
     Total of 9 branches listed
     No landscape branches
-    >>> print_person_code_listing(anon_browser, '/+ownedbranches')
+    >>> print_person_code_listing(anon_browser, 'owned')
     Total of 9 branches listed
     No landscape branches
-    >>> print_person_code_listing(anon_browser, '/+registeredbranches')
+    >>> print_person_code_listing(anon_browser, 'registered')
     Total of 9 branches listed
     No landscape branches
 
     >>> print_person_code_listing(no_priv_browser)
     Total of 9 branches listed
     No landscape branches
-    >>> print_person_code_listing(no_priv_browser, '/+ownedbranches')
+    >>> print_person_code_listing(no_priv_browser, 'owned')
     Total of 9 branches listed
     No landscape branches
-    >>> print_person_code_listing(no_priv_browser, '/+registeredbranches')
+    >>> print_person_code_listing(no_priv_browser, 'registered')
     Total of 9 branches listed
     No landscape branches
 
     >>> print_person_code_listing(landscape_dev_browser)
     Total of 10 branches listed
     lp://dev/~name12/landscape/feature-x            Development   ...
-    >>> print_person_code_listing(landscape_dev_browser, '/+ownedbranches')
+    >>> print_person_code_listing(landscape_dev_browser, 'owned')
     Total of 10 branches listed
     lp://dev/~name12/landscape/feature-x            Development   ...
-    >>> print_person_code_listing(landscape_dev_browser,
-    ...                           '/+registeredbranches')
+    >>> print_person_code_listing(landscape_dev_browser, 'registered')
     Total of 11 branches listed
     lp://dev/~landscape-developers/landscape/trunk  Development   ...
     lp://dev/~name12/landscape/feature-x            Development   ...
@@ -190,10 +194,10 @@
     >>> print_person_code_listing(admin_browser)
     Total of 10 branches listed
     lp://dev/~name12/landscape/feature-x            Development   ...
-    >>> print_person_code_listing(admin_browser, '/+ownedbranches')
+    >>> print_person_code_listing(admin_browser, 'owned')
     Total of 10 branches listed
     lp://dev/~name12/landscape/feature-x            Development   ...
-    >>> print_person_code_listing(admin_browser, '/+registeredbranches')
+    >>> print_person_code_listing(admin_browser, 'registered')
     Total of 11 branches listed
     lp://dev/~landscape-developers/landscape/trunk  Development   ...
     lp://dev/~name12/landscape/feature-x            Development   ...

=== modified file 'lib/lp/code/templates/branch-index.pt'
--- lib/lp/code/templates/branch-index.pt	2012-09-05 23:06:42 +0000
+++ lib/lp/code/templates/branch-index.pt	2014-12-06 12:47:57 +0000
@@ -188,12 +188,6 @@
         replace="context/owner/fmt:displayname">Owner</tal:person>
       </a>
     </li>
-    <li tal:condition="not:view/owner_is_registrant">
-      <a tal:attributes="href string:${context/registrant/fmt:url:code}/+registeredbranches">
-        Other branches registered by <tal:person
-        replace="context/registrant/fmt:displayname">Registrant</tal:person>
-      </a>
-    </li>
   </ul>
 
       </div>

=== modified file 'lib/lp/code/templates/branch-listing.pt'
--- lib/lp/code/templates/branch-listing.pt	2012-11-08 03:55:11 +0000
+++ lib/lp/code/templates/branch-listing.pt	2014-12-06 12:47:57 +0000
@@ -5,7 +5,12 @@
   <form method="get" name="filter" id="filter_form"
         style="padding-bottom: 0.5em"
         tal:attributes="action context/view/form_action|context/request/URL">
-    <label for="field.lifecycle">
+    <tal:category condition="context/view/widgets/category|nothing">
+      <tal:category-selector replace="structure context/view/widgets/category"/>
+      branches with status:
+    </tal:category>
+    <label tal:condition="not:context/view/widgets/category|nothing"
+           for="field.lifecycle">
       Branches with status:
     </label>
     <tal:lifecycle-selector replace="structure context/view/widgets/lifecycle"/>

=== removed file 'lib/lp/code/templates/branch-summary-listing.pt'
--- lib/lp/code/templates/branch-summary-listing.pt	2010-08-23 03:30:54 +0000
+++ lib/lp/code/templates/branch-summary-listing.pt	1970-01-01 00:00:00 +0000
@@ -1,21 +0,0 @@
-<li xmlns:tal="http://xml.zope.org/namespaces/tal";>
-  <div>
-    <a tal:attributes="href context/fmt:url"
-       tal:content="context/bzr_identity"
-       class="sprite branch" >Mozilla Thunderbird 0.9.1</a>
-    <tal:person-link condition="view/show_person_link">
-      - <a tal:replace="structure context/owner/fmt:link:code/+ownedbranches" />
-    </tal:person-link>
-    <tal:product-link condition="view/show_product_link">
-      - <a tal:attributes="href context/product/fmt:url:code/+branches"
-         tal:content="context/product/displayname"
-         class="sprite product">Project</a>
-    </tal:product-link>
-  </div>
-  <div>
-    <tal:revisions condition="context/revision_count">
-      <span tal:replace="context/revision_count">35</span> revisions,
-      <span tal:replace="view/recent_revision_count">31</span> in the past month.<br />
-    </tal:revisions>
-  </div>
-</li>

=== modified file 'lib/lp/code/templates/person-branches.pt'
--- lib/lp/code/templates/person-branches.pt	2012-03-10 13:11:43 +0000
+++ lib/lp/code/templates/person-branches.pt	2014-12-06 12:47:57 +0000
@@ -29,18 +29,8 @@
     <tt class="command">bzr push lp:~<tal:name
       replace="view/user/name"/>/+junk/<em>BRANCHNAME</em></tt>
   </p>
-
-  <div id="no-branch-message" tal:condition="view/is_branch_count_zero">
-    <p tal:content="view/no_branch_message">
-      There are no branches related to Eric the Viking today.
-    </p>
-  </div>
-
-  <tal:has-branches condition="not: view/is_branch_count_zero">
-    <tal:branchlisting
-        content="structure branches/@@+branch-listing" />
-  </tal:has-branches>
-
+  <tal:branchlisting
+      content="structure branches/@@+branch-listing" />
   <tal:teambranches replace="structure context/@@+portlet-teambranches" />
 </div>
 </body>

=== modified file 'lib/lp/code/templates/person-codesummary.pt'
--- lib/lp/code/templates/person-codesummary.pt	2012-10-03 03:41:55 +0000
+++ lib/lp/code/templates/person-codesummary.pt	2014-12-06 12:47:57 +0000
@@ -10,12 +10,6 @@
     <tr class="code-links" tal:condition="menu/owned/enabled">
       <td tal:content="structure menu/owned/render" />
     </tr>
-    <tr class="code-links" tal:condition="menu/registered/enabled">
-      <td tal:content="structure menu/registered/render" />
-    </tr>
-    <tr class="code-links" tal:condition="menu/subscribed/enabled">
-      <td tal:content="structure menu/subscribed/render" />
-    </tr>
     <tr class="code-links"
         tal:condition="menu/active_reviews/enabled">
       <td tal:content="structure menu/active_reviews/render" />

=== modified file 'lib/lp/registry/templates/productseries-codesummary.pt'
--- lib/lp/registry/templates/productseries-codesummary.pt	2012-11-08 03:55:11 +0000
+++ lib/lp/registry/templates/productseries-codesummary.pt	2014-12-06 12:47:57 +0000
@@ -71,7 +71,7 @@
     <a id="series-branch" class="sprite branch"
       tal:attributes="href context/branch/fmt:url"
       tal:content="view/long_bzr_identity">Mozilla Thunderbird 0.9.1</a>
-    - <a tal:replace="structure context/branch/owner/fmt:link:code/+ownedbranches" />
+    - <a tal:replace="structure context/branch/owner/fmt:link:code" />
     <a
       tal:replace="structure context/menu:overview/set_branch/fmt:icon" />
   </div>


Follow ups