Merge lp:~salgado/launchpad/breadcrumbs-for-leafs into lp:launchpad

Proposed by Guilherme Salgado
Status: Merged
Approved by: Brad Crittenden
Approved revision: no longer in the source branch.
Merged at revision: not available
Proposed branch: lp:~salgado/launchpad/breadcrumbs-for-leafs
Merge into: lp:launchpad
Diff against target: None lines
To merge this branch: bzr merge lp:~salgado/launchpad/breadcrumbs-for-leafs
Reviewer Review Type Date Requested Status
Brad Crittenden (community) code Approve
Review via email: mp+10994@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Guilherme Salgado (salgado) wrote :

= Summary =

According to <https://wiki.canonical.com/Launchpad/UI/Navigation>, we'll
want breadcrumbs for the current page when it's not the default one for
the context, so this branch adds that.

== Proposed fix ==

Add a new method to the Hierarchy view, which builds an extra breadcrumb
for the requested page. This breadcrumb is then appended to the list of
breadcrumbs returned by Hierarchy.items

== Implementation details ==

We should be using the page's title as the breadcrumb's text, but that's
not easy to get programmatically as we need the template name to look up
the page title in pagetitles.py. Once our views have a page_title
attribute and we get rid of pagetitles.py, it'll be easy to change the
breadcrumb to use the page's title instead of its name.

== Tests ==

./bin/tests -vvt test_breadcrumbs

== Demo and Q/A ==

= Launchpad lint =

Checking for conflicts. and issues in doctests and templates.
Running jslint, xmllint, pyflakes, and pylint.
Using normal rules.

Linting changed files:
  lib/lp/code/stories/branches/xx-branch-deletion.txt
  lib/canonical/launchpad/webapp/tests/test_breadcrumbs.py
  lib/lp/bugs/stories/bugs/xx-link-bug-to-branch.txt
  lib/lp/bugs/stories/bug-release-management/nomination-navigation.txt
  lib/canonical/launchpad/browser/launchpad.py
  lib/canonical/launchpad/webapp/breadcrumb.py
  lib/lp/bugs/stories/bugs/xx-bug-activity.txt
  lib/lp/registry/stories/foaf/xx-admin-person-review.txt
  lib/lp/registry/stories/product/xx-product-files.txt
  lib/lp/soyuz/stories/soyuz/xx-distribution-archives.txt
  lib/lp/bugs/browser/tests/test_breadcrumbs.py

== Pylint notices ==

lib/canonical/launchpad/browser/launchpad.py
    106: [F0401] Unable to import 'lazr.uri' (No module named uri)

Revision history for this message
Brad Crittenden (bac) wrote :

This change looks great Salgado.

review: Approve (code)
Revision history for this message
Brad Crittenden (bac) wrote :

This change looks great Salgado.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/launchpad/browser/launchpad.py'
2--- lib/canonical/launchpad/browser/launchpad.py 2009-08-26 07:59:49 +0000
3+++ lib/canonical/launchpad/browser/launchpad.py 2009-09-01 18:06:25 +0000
4@@ -34,6 +34,7 @@
5 import urllib
6 from datetime import timedelta, datetime
7
8+from zope.app import zapi
9 from zope.datetime import parseDatetimetz, tzinfo, DateTimeError
10 from zope.component import getUtility, queryAdapter
11 from zope.interface import implements
12@@ -96,6 +97,7 @@
13 LaunchpadFormView, LaunchpadView, Link, Navigation,
14 StandardLaunchpadFacets, canonical_name, canonical_url, custom_widget,
15 stepto)
16+from canonical.launchpad.webapp.breadcrumb import Breadcrumb
17 from canonical.launchpad.webapp.interfaces import (
18 IBreadcrumb, ILaunchBag, ILaunchpadRoot, INavigationMenu,
19 NotFoundError, POSTToNonCanonicalURL)
20@@ -234,25 +236,48 @@
21 breadcrumbs.append(breadcrumb)
22
23 host = URI(self.request.getURL()).host
24- if (len(breadcrumbs) == 0
25- or host == allvhosts.configs['mainsite'].hostname):
26- return breadcrumbs
27-
28- # If we got this far it means we have breadcrumbs and we're not on the
29- # mainsite, so we'll sneak an extra breadcrumb for the vhost we're on.
30- vhost = host.split('.')[0]
31-
32- # Iterate over the context of our breadcrumbs in reverse order and for
33- # the first one we find an adapter named after the vhost we're on,
34- # generate an extra breadcrumb and insert it in our list.
35- for idx, breadcrumb in reversed(list(enumerate(breadcrumbs))):
36- extra_breadcrumb = queryAdapter(
37- breadcrumb.context, IBreadcrumb, name=vhost)
38- if extra_breadcrumb is not None:
39- breadcrumbs.insert(idx + 1, extra_breadcrumb)
40- break
41+ mainhost = allvhosts.configs['mainsite'].hostname
42+ if len(breadcrumbs) != 0 and host != mainhost:
43+ # We have breadcrumbs and we're not on the mainsite, so we'll
44+ # sneak an extra breadcrumb for the vhost we're on.
45+ vhost = host.split('.')[0]
46+
47+ # Iterate over the context of our breadcrumbs in reverse order and
48+ # for the first one we find an adapter named after the vhost we're
49+ # on, generate an extra breadcrumb and insert it in our list.
50+ for idx, breadcrumb in reversed(list(enumerate(breadcrumbs))):
51+ extra_breadcrumb = queryAdapter(
52+ breadcrumb.context, IBreadcrumb, name=vhost)
53+ if extra_breadcrumb is not None:
54+ breadcrumbs.insert(idx + 1, extra_breadcrumb)
55+ break
56+ if len(breadcrumbs):
57+ page_crumb = self.makeBreadcrumbForRequestedPage()
58+ if page_crumb:
59+ breadcrumbs.append(page_crumb)
60 return breadcrumbs
61
62+ def makeBreadcrumbForRequestedPage(self):
63+ """Return an `IBreadcrumb` for the requested page.
64+
65+ The `IBreadcrumb` for the requested page is created using the current
66+ URL and the page's name (i.e. the last path segment of the URL).
67+
68+ If the requested page (as specified in self.request) is the default
69+ one for the last traversed object, return None.
70+ """
71+ url = self.request.getURL()
72+ last_segment = URI(url).path.split('/')[-1]
73+ default_view_name = zapi.getDefaultViewName(
74+ self.request.traversed_objects[-1], self.request)
75+ if last_segment.startswith('+') and last_segment != default_view_name:
76+ breadcrumb = Breadcrumb(None)
77+ breadcrumb._url = url
78+ breadcrumb.text = last_segment
79+ return breadcrumb
80+ else:
81+ return None
82+
83 @property
84 def display_breadcrumbs(self):
85 """Return whether the breadcrumbs should be displayed."""
86@@ -260,6 +285,7 @@
87 # to display it as it will simply repeat the context.title.
88 return len(self.items) > 1
89
90+
91 class MaintenanceMessage:
92 """Display a maintenance message if the control file is present and
93 it contains a valid iso format time.
94
95=== modified file 'lib/canonical/launchpad/webapp/breadcrumb.py'
96--- lib/canonical/launchpad/webapp/breadcrumb.py 2009-08-25 12:35:23 +0000
97+++ lib/canonical/launchpad/webapp/breadcrumb.py 2009-08-31 17:40:56 +0000
98@@ -27,6 +27,7 @@
99 implements(IBreadcrumb)
100
101 text = None
102+ _url = None
103
104 def __init__(self, context):
105 self.context = context
106@@ -46,7 +47,10 @@
107
108 @property
109 def url(self):
110- return canonical_url(self.context, rootsite=self.rootsite)
111+ if self._url is None:
112+ return canonical_url(self.context, rootsite=self.rootsite)
113+ else:
114+ return self._url
115
116 @property
117 def icon(self):
118
119=== modified file 'lib/canonical/launchpad/webapp/tests/test_breadcrumbs.py'
120--- lib/canonical/launchpad/webapp/tests/test_breadcrumbs.py 2009-08-25 12:35:23 +0000
121+++ lib/canonical/launchpad/webapp/tests/test_breadcrumbs.py 2009-08-31 17:40:56 +0000
122@@ -37,6 +37,29 @@
123 self.assertEquals(Breadcrumb(cookbook).rootsite, 'cooking')
124
125
126+class TestExtraBreadcrumbForLeafPageOnHierarchyView(BaseBreadcrumbTestCase):
127+ """When the current page is not the object's default one (+index), we add
128+ an extra breadcrumb for it.
129+ """
130+
131+ def setUp(self):
132+ super(TestExtraBreadcrumbForLeafPageOnHierarchyView, self).setUp()
133+ login('test@canonical.com')
134+ self.product = self.factory.makeProduct(name='crumb-tester')
135+ self.product_url = canonical_url(self.product)
136+
137+ def test_default_page(self):
138+ urls = self._getBreadcrumbsURLs(
139+ self.product_url, [self.root, self.product])
140+ self.assertEquals(urls, [self.product_url])
141+
142+ def test_non_default_page(self):
143+ downloads_url = "%s/+download" % self.product_url
144+ urls = self._getBreadcrumbsURLs(
145+ downloads_url, [self.root, self.product])
146+ self.assertEquals(urls, [self.product_url, downloads_url])
147+
148+
149 class TestExtraVHostBreadcrumbsOnHierarchyView(BaseBreadcrumbTestCase):
150 """How our breadcrumbs behave when using a vhost other than the main one?
151
152
153=== modified file 'lib/lp/bugs/browser/tests/test_breadcrumbs.py'
154--- lib/lp/bugs/browser/tests/test_breadcrumbs.py 2009-08-24 20:28:33 +0000
155+++ lib/lp/bugs/browser/tests/test_breadcrumbs.py 2009-08-31 17:40:56 +0000
156@@ -35,9 +35,11 @@
157 url = canonical_url(
158 self.bug.default_bugtask, rootsite='bugs', view_name='+activity')
159 urls = self._getBreadcrumbsURLs(url, self.traversed_objects)
160- self.assertEquals(urls[-1], self.bugtask_url)
161+ self.assertEquals(urls[-1], "%s/+activity" % self.bugtask_url)
162+ self.assertEquals(urls[-2], self.bugtask_url)
163 texts = self._getBreadcrumbsTexts(url, self.traversed_objects)
164- self.assertEquals(texts[-1], "Bug #%d" % self.bug.id)
165+ self.assertEquals(texts[-1], "+activity")
166+ self.assertEquals(texts[-2], "Bug #%d" % self.bug.id)
167
168 def test_bugtask_private_bug(self):
169 # A breadcrumb is not generated for a bug that the user does
170@@ -45,8 +47,7 @@
171 login('foo.bar@canonical.com')
172 self.bug.setPrivate(True, self.bug.owner)
173 login(ANONYMOUS)
174- url = canonical_url(
175- self.bug.default_bugtask, rootsite='bugs', view_name='+activity')
176+ url = canonical_url(self.bug.default_bugtask, rootsite='bugs')
177 self.assertEquals(
178 ['http://launchpad.dev/crumb-tester',
179 'http://bugs.launchpad.dev/crumb-tester'],
180
181=== modified file 'lib/lp/bugs/stories/bug-release-management/nomination-navigation.txt'
182--- lib/lp/bugs/stories/bug-release-management/nomination-navigation.txt 2009-08-27 08:32:39 +0000
183+++ lib/lp/bugs/stories/bug-release-management/nomination-navigation.txt 2009-09-01 17:31:05 +0000
184@@ -12,7 +12,7 @@
185 ... '/nominations/2/+editstatus')
186 >>> print_location(admin_browser.contents)
187 Hierarchy: Ubuntu > ?mozilla-firefox? package >
188- Bugs on mozilla-firefox > Bug #1
189+ Bugs on mozilla-firefox > Bug #1...
190 Tabs:
191 * Overview - http://launchpad.dev/ubuntu/+source/mozilla-firefox
192 * Code - http://code.launchpad.dev/ubuntu/+source/mozilla-firefox
193
194=== modified file 'lib/lp/bugs/stories/bugs/xx-bug-activity.txt'
195--- lib/lp/bugs/stories/bugs/xx-bug-activity.txt 2009-08-28 14:31:15 +0000
196+++ lib/lp/bugs/stories/bugs/xx-bug-activity.txt 2009-09-01 17:31:05 +0000
197@@ -7,13 +7,12 @@
198 ... 'mozilla-firefox/+bug/3/+activity')
199 >>> main_content = find_main_content(anon_browser.contents)
200
201-
202 The page contains a link back to the bug page in the breadcrumbs and
203 the main heading repeats the bug number for clarity:
204
205 >>> print_location(anon_browser.contents)
206 Hierarchy: Debian > ?mozilla-firefox? package >
207- Bugs on mozilla-firefox > Bug #3
208+ Bugs on mozilla-firefox > Bug #3...
209 Tabs:
210 * Overview - http://launchpad.dev/debian/+source/mozilla-firefox
211 * Code - http://code.launchpad.dev/debian/+source/mozilla-firefox
212
213=== modified file 'lib/lp/bugs/stories/bugs/xx-link-bug-to-branch.txt'
214--- lib/lp/bugs/stories/bugs/xx-link-bug-to-branch.txt 2009-08-28 11:12:06 +0000
215+++ lib/lp/bugs/stories/bugs/xx-link-bug-to-branch.txt 2009-09-01 17:31:05 +0000
216@@ -57,6 +57,7 @@
217 >>> link.click()
218 >>> print extract_text(find_tag_by_id(
219 ... user_browser.contents, 'maincontent'))
220+ Mozilla Firefox...
221 Remove bug branch link
222 Are you sure you want to remove the link between Bug #1: Firefox does
223 not support SVG and the branch lp://dev/~name12/firefox/main?
224
225=== modified file 'lib/lp/code/stories/branches/xx-branch-deletion.txt'
226--- lib/lp/code/stories/branches/xx-branch-deletion.txt 2009-08-12 00:58:24 +0000
227+++ lib/lp/code/stories/branches/xx-branch-deletion.txt 2009-09-01 17:31:05 +0000
228@@ -25,6 +25,7 @@
229 >>> delete_link.click()
230 >>> print extract_text(find_main_content(browser.contents))
231 Delete branch
232+ Mozilla Firefox...
233 Branch deletion is permanent.
234 or Cancel
235
236@@ -68,7 +69,7 @@
237 >>> admin_browser.open(branch_location)
238 >>> admin_browser.getLink('Delete branch').click()
239 >>> print extract_text(find_main_content(admin_browser.contents))
240- Delete branch
241+ Delete branch...
242 This branch cannot be deleted as it has 1 branch sharing revisions.
243
244 However, you can delete a branch that's the official branch of a source
245
246=== modified file 'lib/lp/registry/stories/foaf/xx-admin-person-review.txt'
247--- lib/lp/registry/stories/foaf/xx-admin-person-review.txt 2009-08-01 00:02:55 +0000
248+++ lib/lp/registry/stories/foaf/xx-admin-person-review.txt 2009-09-01 17:31:05 +0000
249@@ -47,7 +47,7 @@
250
251 The page also contains a link back to the +review page.
252
253- >>> link = admin_browser.getLink(url='+review')
254+ >>> link = admin_browser.getLink(url='+review', index=1)
255 >>> print link.text
256 edit[IMG] Review the user's Launchpad information
257
258
259=== modified file 'lib/lp/registry/stories/product/xx-product-files.txt'
260--- lib/lp/registry/stories/product/xx-product-files.txt 2009-08-27 17:50:21 +0000
261+++ lib/lp/registry/stories/product/xx-product-files.txt 2009-09-01 17:31:05 +0000
262@@ -98,6 +98,7 @@
263 >>> main_content = find_main_content(tbird_owner.contents)
264 >>> print extract_text(main_content)
265 Download project files
266+ Mozilla Thunderbird...
267 No download files exist for this project...
268 Add download file for release: 0.8
269 >>> tbird_owner.getControl('Delete Files')
270
271=== modified file 'lib/lp/soyuz/stories/soyuz/xx-distribution-archives.txt'
272--- lib/lp/soyuz/stories/soyuz/xx-distribution-archives.txt 2009-08-12 13:00:09 +0000
273+++ lib/lp/soyuz/stories/soyuz/xx-distribution-archives.txt 2009-09-01 17:31:05 +0000
274@@ -52,6 +52,7 @@
275 >>> main_content = find_main_content(anon_browser.contents)
276 >>> print extract_text(main_content)
277 Copy Archives related to Ubuntu Linux
278+ Ubuntu...
279 'Copy' archives containing packages copied from other archives
280 (the main archive or PPAs) for a distribution.
281 ...
282