Merge lp:~sinzui/launchpad/team-titles into lp:launchpad

Proposed by Curtis Hovey
Status: Merged
Approved by: Curtis Hovey
Approved revision: no longer in the source branch.
Merged at revision: 14862
Proposed branch: lp:~sinzui/launchpad/team-titles
Merge into: lp:launchpad
Diff against target: 1171 lines (+277/-186)
34 files modified
lib/lp/app/browser/tales.py (+17/-8)
lib/lp/app/browser/tests/test_base_layout.py (+2/-1)
lib/lp/app/browser/tests/test_formatters.py (+77/-5)
lib/lp/bugs/browser/bugtask.py (+10/-2)
lib/lp/bugs/browser/configure.zcml (+6/-0)
lib/lp/bugs/browser/structuralsubscription.py (+2/-6)
lib/lp/bugs/browser/tests/test_breadcrumbs.py (+22/-1)
lib/lp/bugs/browser/tests/test_expose.py (+26/-17)
lib/lp/bugs/stories/bug-also-affects/xx-also-affects-new-upstream.txt (+3/-3)
lib/lp/bugs/stories/bugattachments/xx-attachments-to-bug-report.txt (+1/-1)
lib/lp/bugs/stories/bugs/xx-bug-comments-truncated.txt (+3/-3)
lib/lp/bugs/stories/bugs/xx-bug-create-question.txt (+9/-11)
lib/lp/bugs/stories/bugs/xx-bug-obfuscation.txt (+2/-2)
lib/lp/bugs/stories/bugtask-searches/xx-advanced-upstream-pending-bugwatch.txt (+1/-1)
lib/lp/bugs/stories/bugtask-searches/xx-person-bugs.txt (+6/-6)
lib/lp/bugs/stories/bugtracker/xx-bugtracker.txt (+4/-6)
lib/lp/bugs/stories/guided-filebug/xx-filebug-attachments.txt (+1/-1)
lib/lp/bugs/stories/guided-filebug/xx-product-guided-filebug.txt (+2/-4)
lib/lp/bugs/stories/guided-filebug/xx-project-guided-filebug.txt (+2/-4)
lib/lp/code/stories/branches/xx-branch-index.txt (+1/-1)
lib/lp/hardwaredb/stories/hwdb/xx-hwdb.txt (+1/-1)
lib/lp/registry/model/person.py (+7/-1)
lib/lp/registry/stories/distroseries/xx-distroseries-index.txt (+1/-1)
lib/lp/registry/stories/milestone/object-milestones.txt (+8/-85)
lib/lp/registry/stories/milestone/xx-milestone-add-and-edit.txt (+1/-1)
lib/lp/registry/stories/person/xx-person-subscriptions.txt (+3/-3)
lib/lp/registry/stories/productseries/xx-productseries-add-and-edit.txt (+1/-1)
lib/lp/registry/stories/project/xx-project-index.txt (+1/-1)
lib/lp/registry/tests/test_person.py (+12/-0)
lib/lp/services/webapp/breadcrumb.py (+11/-1)
lib/lp/services/webapp/error.py (+0/-6)
lib/lp/services/webapp/interfaces.py (+2/-0)
lib/lp/services/webapp/tests/test_breadcrumbs.py (+31/-1)
lib/lp/translations/stories/standalone/xx-person-activity.txt (+1/-1)
To merge this branch: bzr merge lp:~sinzui/launchpad/team-titles
Reviewer Review Type Date Requested Status
Steve Kowalik (community) code Approve
Review via email: mp+93675@code.launchpad.net

Description of the change

Use standard page titles, bread crumbs, and headings for teams and bugs.

    Pre-implementation: No one. I largely followed the advise of mpt in
    the bug reports.

Bugs Bug #244558 Not obvious that teams can't have their own bug reports
    Page does not clearly state you are looking at a team. The
    page title, heading, and bread crumbs do not conform to Lp rules.
    Many users mistake the team for a project :(

Bug #516485 No User Name in the Title of Related Bugs Page Users/team
    The bug vhost breadcrumb adapter is not registered for IPerson. The
    adaption fails during traversal every time. When a view is careful
    to provide page_title, a few crumbs appear.

Bug #928234 Team application pages aren't obviously about a team
    Application page headings do not state you are looking at a team.
    mpt suggests that we smartquote the team and append 'team' as is
    done in the bread crumbs.

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

RULES

    * All four bugs are are caused by developer confusion about how
      Lp page titles, bread crumbs and headings work. In some
      cases the views intentionally deviate from Lp rules.
    * Remove support for override_title_breadcrumbs
      * Instead check for an instances SystemErrorView.
      * Removing override_title_breadcrumbs will restore the page title
        and breadcrumbs to several bug pages and specifically team bug
        pages.
      * This partially addresses the concern that bug titles can leak
        confidential information when the user has limited view.
    * Every traversed object must have a breadcrumb adapter.
      * Register BugsVHostBreadcrumb for IPerson
    * The Person bug views must provide:
      * A terse page_title to create a proper page title and breadcrumb.
      * An informative label that explains the purpose the page as the <h1>

QA

    * Visit https://bugs.qastaging.launchpad.net/launchpad/+subscribe
    * Verify the breascrumbs read
      Launchpad itself >> Bugs >> Subscribe
    * Verify the page title is
      Subscribe : Bugs : Launchpad itself

    * Visit https://bugs.qastaging.launchpad.net/launchpad/+bug/533044
    * Verify that the page title is
      Bug #533044 <title> : Bugs : Launchpad itself

    * Visit https://bugs.qastaging.launchpad.net/launchpad/+bug/a77
    * Verify that the page title is
      Error: Page not found

    * Visit https://bugs.qastaging.launchpad.net/~launchpad
    * Verify the page title is
      Bugs : "Canonical Launchpad Engineering" team
    * Verify the breadcrumbs are
      "Canonical Launchpad Engineering" team >> Bugs
    * Verify the first heading is
      "Canonical Launchpad Engineering" team

    * Visit https://bugs.qastaging.launchpad.net/~launchpad/+assignedbugs
    * Verify the page title is
      Assigned bugs : Bugs : "Canonical Launchpad Engineering" team
    * Verify the breadcrumbs are
      "Canonical Launchpad Engineering" team >> Bugs >> Assigned bugs

    * Visit https://qastaging.launchpad.net/~launchpad
    * Verify the heading is "Canonical Launchpad Engineering" team

    * Visit https://answers.qastaging.launchpad.net/~launchpad
    * Verify the first heading is "Canonical Launchpad Engineering" team

    * Visit https://code.qastaging.launchpad.net/~launchpad
    * Verify the first heading is "Canonical Launchpad Engineering" team

    * Visit https://blueprints.qastaging.launchpad.net/~launchpad
    * Verify the first heading is "Canonical Launchpad Engineering" team

    * Visit https://translations.qastaging.launchpad.net/~launchpad
    * Verify the first heading is "Canonical Launchpad Engineering" team

LINT

    lib/lp/app/browser/tales.py
    lib/lp/bugs/browser/bugtask.py
    lib/lp/bugs/browser/structuralsubscription.py
    lib/lp/bugs/stories/bug-also-affects/xx-also-affects-new-upstream.txt
    lib/lp/bugs/stories/bugattachments/xx-attachments-to-bug-report.txt
    lib/lp/bugs/stories/bugs/xx-bug-comments-truncated.txt
    lib/lp/bugs/stories/bugs/xx-bug-create-question.txt
    lib/lp/bugs/stories/bugs/xx-bug-obfuscation.txt
    lib/lp/bugs/stories/bugtask-searches/xx-advanced-upstream-pending-bugwatch.txt
    lib/lp/bugs/stories/guided-filebug/xx-filebug-attachments.txt
    lib/lp/bugs/stories/guided-filebug/xx-product-guided-filebug.txt
    lib/lp/bugs/stories/guided-filebug/xx-project-guided-filebug.txt
    lib/lp/services/webapp/error.py
    lib/lp/bugs/browser/configure.zcml
    lib/lp/bugs/browser/tests/test_breadcrumbs.py
    ^ Lint is not happy with some of the stories. I can fix these after the
      review.

TEST

    ./bin/test -vvc -t xx-also-affects-new-upstream \
        -t xx-attachments-to-bug-report -t xx-bug-comments-truncated \
        -t xx-bug-create-question -t xx-bug-obfuscation \
        -t xx-advanced-upstream-pending-bugwatch \
        -t xx-filebug-attachments -t xx-product-guided-filebug \
        -t xx-project-guided-filebug lp.bugs.tests.test_doc
    ./bin/test -vvc lp.bugs.browser.tests.test_breadcrumbs
    ./bin/test -vvc -t test_title lp.registry.tests.test_person

IMPLEMENTATION

I replaced the check for override_title_breadcrumb in ObjectFormatterAPI
to instead check if the view is an instance of SystemErrorView. I
removed all override_title_breadcrumb attributes from the error views
and the two offending bugs views. I updated many tests, and it is clear
that the ellipsis in the stories was hiding the title insanity.
    lib/lp/app/browser/tales.py
    lib/lp/bugs/browser/bugtask.py
    lib/lp/bugs/browser/structuralsubscription.py
    lib/lp/bugs/stories/bug-also-affects/xx-also-affects-new-upstream.txt
    lib/lp/bugs/stories/bugattachments/xx-attachments-to-bug-report.txt
    lib/lp/bugs/stories/bugs/xx-bug-comments-truncated.txt
    lib/lp/bugs/stories/bugs/xx-bug-create-question.txt
    lib/lp/bugs/stories/bugs/xx-bug-obfuscation.txt
    lib/lp/bugs/stories/bugtask-searches/xx-advanced-upstream-pending-bugwatch.txt
    lib/lp/bugs/stories/guided-filebug/xx-filebug-attachments.txt
    lib/lp/bugs/stories/guided-filebug/xx-product-guided-filebug.txt
    lib/lp/bugs/stories/guided-filebug/xx-project-guided-filebug.txt
    lib/lp/services/webapp/error.py

Bug #516485 is caused by a broken breadcrumb adaption.
BugsVHostBreadcrumb was not registered for IPerson. Adding it made
user/team page titles and breadcrumbs behave like projects and distros.
I added a test to verify the breadcrumb adapter makes the expected crumb.
    lib/lp/bugs/browser/configure.zcml
    lib/lp/bugs/browser/tests/test_breadcrumbs.py

Bug 928234 is solved by ensuring that team.title returns the smartquoted
displayname with team appended to it. I added a test, but I believe there
will be some test failures that I will need to follow up on. I may also
be able to remove numerous calls to smartquote in views now that .title
provides the definitive formatting.
    lib/lp/registry/model/person.py
    lib/lp/registry/tests/test_person.py

To post a comment you must log in.
Revision history for this message
Robert Collins (lifeless) wrote :

On Sun, Feb 19, 2012 at 3:41 AM, Curtis Hovey
<email address hidden> wrote:
> Bug #533044 Resummarizing bug report doesn't change page title
>     Changing the bug title does not update the page title...
>     but the bug title should not be in the page title.

This bit does not make sense to me. Open a few bugs. Hit ctrl-shift-E
to get a list of tabs; having the title in the page title is extremely
useful.

Revision history for this message
Curtis Hovey (sinzui) wrote :

Bug titles are not terse. We may want them to be a one line summary, but users place between 0 and 50k characters in it. Each browser engine implements it own rules for handling pathological titles on top the native rule that further restrict the length of text in a menu (or completion). So while we know the practical limit of the code is 64k of title, the UI rules place the limit closer to 128 characters. The W3c weighed in on the issue and recommends 64 characters.

Launchpad UI rule forbid us from doing something stupid like duplicating the heading in the bread crumbs beneath the heading so that the user can read it twice.

We could change bug titles to be what we mean but that is a separate group of bugs. I can put a truncated portion on the bug title into the page title, but that truncation will appear under the untruncated heading. Well that is not exactly true, because the bread crumbs have there own truncation rules at 40 characters.

If you really want part of the bug title in the page title, I think we want modify the BugFormatterAPI.page_title to extend the rules by injecting 40 or 64 characters of the bug title into the page title.

Revision history for this message
Robert Collins (lifeless) wrote :

On Mon, Feb 20, 2012 at 5:27 AM, Curtis Hovey
<email address hidden> wrote:
> We could change bug titles to be what we mean but that is a separate group of bugs. I can put a truncated portion on the bug title into the page title, but that truncation will appear under the untruncated heading. Well that is not exactly true, because the bread crumbs have there own truncation rules at 40 characters.
>
> If you really want part of the bug title in the page title, I think we want modify the BugFormatterAPI.page_title to extend the rules by injecting 40 or 64 characters of the bug title into the page title.

I have a clear memory of previous debates around page title and bug
titles; I think we should keep it there unless we do some testing to
see whether users really do value it (I believe they do...).

We could do that testing via A/B routes, or user testing or whatever.

-> how do you feel about doing the 40 or 64 characters in the title
(I'd suggest 64) for now ?

-Rob

Revision history for this message
Curtis Hovey (sinzui) wrote :

On 02/19/2012 02:07 PM, Robert Collins wrote:
> -> how do you feel about doing the 40 or 64 characters in the title
> (I'd suggest 64) for now ?

I have a sketch of adding artefact titles to page titles. I will use it for bugs and it can be applied to questions, faqs, and blueprints.

I will update the ObjectFormatterAPI.pagetitle to look add the extra text from page_title_extra which will follow Lp rules while permitting objects to make adjustments as needed. I think I just need one test to verify the rules are called and a test to verify that BugFormatterAPI define page_title_extra.

Revision history for this message
William Grant (wgrant) wrote :

There's also the issue of search engine results. They tend to use the page title as the primary link text.

Revision history for this message
Curtis Hovey (sinzui) wrote :

I extended Ibreadcrumb to provide .detail which can be used for page titles and link titles that need rich informative text. Breadcrumb.detail will return .text if detail is not defines. The BugTaskBreadcrumb does define detail to provide the bug displayname and title. ObjectFormatterAPI.pagetitle uses .detail of the last breadcrumb to create a rich starting fragment for the beadcrumbs. This is a nice non-crack solution where objects can suggest better text, but cannot say STFU to the Lp rules.

Revision history for this message
Steve Kowalik (stevenk) wrote :

This looks like brilliant work.

Bug titles are now missing a colon, please correct before that landing.

My nitpicks are your import additions to lib/lp/registry/model/person.py and lib/lp/registry/tests/test_person.py -- pytz, storm and lazr.* should be in the second paragraph of imports, like so:

<base Python imports>

<Things we pull in, such as LAZR, Storm and pytz>

<lp.*>

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/app/browser/tales.py'
2--- lib/lp/app/browser/tales.py 2012-02-15 03:59:49 +0000
3+++ lib/lp/app/browser/tales.py 2012-02-23 16:31:19 +0000
4@@ -78,6 +78,7 @@
5 )
6 from lp.services.webapp.authorization import check_permission
7 from lp.services.webapp.canonicalurl import nearest_adapter
8+from lp.services.webapp.error import SystemErrorView
9 from lp.services.webapp.interfaces import (
10 IApplicationMenu,
11 IContextMenu,
12@@ -666,22 +667,24 @@
13 else:
14 return 'public'
15
16+ def _getSaneBreadcrumbDetail(self, breadcrumb):
17+ text = breadcrumb.detail
18+ if len(text) > 64:
19+ return '%s...' % text[0:64]
20+ return text
21+
22 def pagetitle(self):
23 """The page title to be used.
24
25 By default, reverse breadcrumbs are always used if they are available.
26 If not available, then the view's .page_title attribut is used.
27- If breadcrumbs are available, then a view can still choose to
28- override them by setting the attribute .override_title_breadcrumbs
29- to True.
30 """
31 ROOT_TITLE = 'Launchpad'
32 view = self._context
33 request = get_current_browser_request()
34 hierarchy_view = getMultiAdapter(
35 (view.context, request), name='+hierarchy')
36- override = getattr(view, 'override_title_breadcrumbs', False)
37- if (override or
38+ if (isinstance(view, SystemErrorView) or
39 hierarchy_view is None or
40 not hierarchy_view.display_breadcrumbs):
41 # The breadcrumbs are either not available or are overridden. If
42@@ -697,9 +700,15 @@
43 if template is None:
44 return ROOT_TITLE
45 # Use the reverse breadcrumbs.
46- return SEPARATOR.join(
47- breadcrumb.text for breadcrumb
48- in reversed(hierarchy_view.items))
49+ breadcrumbs = list(reversed(hierarchy_view.items))
50+ if len(breadcrumbs) == 0:
51+ # This implies there are no breadcrumbs, but this more often
52+ # is caused when an Unauthorized error is being raised.
53+ return ''
54+ detail_breadcrumb = self._getSaneBreadcrumbDetail(breadcrumbs[0])
55+ title_breadcrumbs = [breadcrumb.text for breadcrumb in breadcrumbs[1:]]
56+ title_text = SEPARATOR.join([detail_breadcrumb] + title_breadcrumbs)
57+ return FormattersAPI(title_text).obfuscate_email()
58
59
60 class ObjectImageDisplayAPI:
61
62=== modified file 'lib/lp/app/browser/tests/test_base_layout.py'
63--- lib/lp/app/browser/tests/test_base_layout.py 2012-01-01 02:58:52 +0000
64+++ lib/lp/app/browser/tests/test_base_layout.py 2012-02-23 16:31:19 +0000
65@@ -108,9 +108,10 @@
66 self.assertEqual('watermark-apps-portlet', watermark['class'])
67 if self.context.is_team:
68 self.assertEqual('/@@/team-logo', watermark.img['src'])
69+ self.assertEqual(u'\u201cWaffles\u201d team', watermark.h2.string)
70 else:
71 self.assertEqual('/@@/person-logo', watermark.img['src'])
72- self.assertEqual('Waffles', watermark.h2.string)
73+ self.assertEqual('Waffles', watermark.h2.string)
74 self.assertEqual('facetmenu', watermark.ul['class'])
75
76 def test_main_side(self):
77
78=== modified file 'lib/lp/app/browser/tests/test_formatters.py'
79--- lib/lp/app/browser/tests/test_formatters.py 2012-01-01 02:58:52 +0000
80+++ lib/lp/app/browser/tests/test_formatters.py 2012-02-23 16:31:19 +0000
81@@ -5,10 +5,82 @@
82
83 __metaclass__ = type
84
85-from lp.app.browser.tales import PillarFormatterAPI
86-from lp.services.webapp import canonical_url
87-from lp.testing import TestCaseWithFactory
88+from lp.app.browser.tales import (
89+ ObjectFormatterAPI,
90+ PillarFormatterAPI,
91+ )
92+from lp.services.webapp.publisher import canonical_url
93+from lp.testing import (
94+ FakeAdapterMixin,
95+ TestCaseWithFactory,
96+ )
97 from lp.testing.layers import DatabaseFunctionalLayer
98+from lp.testing.views import create_view
99+
100+
101+class ObjectFormatterAPITestCase(TestCaseWithFactory, FakeAdapterMixin):
102+
103+ layer = DatabaseFunctionalLayer
104+
105+ def test_pagetitle_top_level(self):
106+ project = self.factory.makeProduct(name='fnord')
107+ view = create_view(project, name='+index', current_request=True)
108+ view.request.traversed_objects = [project, view]
109+ formatter = ObjectFormatterAPI(view)
110+ self.assertEqual('Fnord in Launchpad', formatter.pagetitle())
111+
112+ def test_pagetitle_vhost(self):
113+ project = self.factory.makeProduct(name='fnord')
114+ view = create_view(project, name='+bugs', rootsite='bugs',
115+ current_request=True, server_url='https://bugs.launchpad.dev/')
116+ view.request.traversed_objects = [project, view]
117+ formatter = ObjectFormatterAPI(view)
118+ self.assertEqual('Bugs : Fnord', formatter.pagetitle())
119+
120+ def test_pagetitle_lower_level_default_view(self):
121+ project = self.factory.makeProduct(name='fnord')
122+ view = create_view(
123+ project.development_focus, name='+index', current_request=True)
124+ view.request.traversed_objects = [
125+ project, project.development_focus, view]
126+ formatter = ObjectFormatterAPI(view)
127+ self.assertEqual('Series trunk : Fnord', formatter.pagetitle())
128+
129+ def test_pagetitle_lower_level_named_view(self):
130+ project = self.factory.makeProduct(name='fnord')
131+ view = create_view(
132+ project.development_focus, name='+edit', current_request=True)
133+ view.request.traversed_objects = [
134+ project, project.development_focus, view]
135+ formatter = ObjectFormatterAPI(view)
136+ self.assertEqual(
137+ 'Edit Fnord trunk series : Series trunk : Fnord',
138+ formatter.pagetitle())
139+
140+ def test_pagetitle_last_breadcrumb_detail(self):
141+ project = self.factory.makeProduct(name='fnord')
142+ bug = self.factory.makeBug(product=project, title='bang')
143+ view = create_view(
144+ bug.bugtasks[0], name='+index', rootsite='bugs',
145+ current_request=True, server_url='https://bugs.launchpad.dev/')
146+ view.request.traversed_objects = [project, bug.bugtasks[0], view]
147+ formatter = ObjectFormatterAPI(view)
148+ self.assertEqual(
149+ u'%s \u201cbang\u201d : Bugs : Fnord' % bug.displayname,
150+ formatter.pagetitle())
151+
152+ def test_pagetitle_last_breadcrumb_detail_too_long(self):
153+ project = self.factory.makeProduct(name='fnord')
154+ title = 'Bang out go the lights ' * 4
155+ bug = self.factory.makeBug(product=project, title=title)
156+ view = create_view(
157+ bug.bugtasks[0], name='+index', rootsite='bugs',
158+ current_request=True, server_url='https://bugs.launchpad.dev/')
159+ view.request.traversed_objects = [project, bug.bugtasks[0], view]
160+ formatter = ObjectFormatterAPI(view)
161+ detail = u'%s \u201c%s\u201d' % (bug.displayname, title)
162+ expected_title = u'%s... : Bugs : Fnord' % detail[0:64]
163+ self.assertEqual(expected_title, formatter.pagetitle())
164
165
166 class TestPillarFormatterAPI(TestCaseWithFactory):
167@@ -34,7 +106,7 @@
168 'summary': self.product.displayname,
169 'css_class': self.FORMATTER_CSS_CLASS,
170 }
171- self.assertEquals(link, template % mapping)
172+ self.assertEqual(link, template % mapping)
173
174 def test_link_with_displayname(self):
175 # Calling PillarFormatterAPI.link_with_displayname() will return
176@@ -52,4 +124,4 @@
177 'name': self.product.name,
178 'css_class': self.FORMATTER_CSS_CLASS,
179 }
180- self.assertEquals(link, template % mapping)
181+ self.assertEqual(link, template % mapping)
182
183=== modified file 'lib/lp/bugs/browser/bugtask.py'
184--- lib/lp/bugs/browser/bugtask.py 2012-02-22 05:22:13 +0000
185+++ lib/lp/bugs/browser/bugtask.py 2012-02-23 16:31:19 +0000
186@@ -638,8 +638,6 @@
187 class BugTaskView(LaunchpadView, BugViewMixin, FeedsMixin):
188 """View class for presenting information about an `IBugTask`."""
189
190- override_title_breadcrumbs = True
191-
192 def __init__(self, context, request):
193 LaunchpadView.__init__(self, context, request)
194
195@@ -655,6 +653,10 @@
196
197 @property
198 def page_title(self):
199+ return self.context.bug.id
200+
201+ @property
202+ def label(self):
203 heading = 'Bug #%s in %s' % (
204 self.context.bug.id, self.context.bugtargetdisplayname)
205 title = FormattersAPI(self.context.bug.title).obfuscate_email()
206@@ -4508,3 +4510,9 @@
207 @property
208 def text(self):
209 return self.context.bug.displayname
210+
211+ @property
212+ def detail(self):
213+ bug = self.context.bug
214+ title = smartquote('"%s"' % bug.title)
215+ return '%s %s' % (bug.displayname, title)
216
217=== modified file 'lib/lp/bugs/browser/configure.zcml'
218--- lib/lp/bugs/browser/configure.zcml 2012-02-20 05:12:41 +0000
219+++ lib/lp/bugs/browser/configure.zcml 2012-02-23 16:31:19 +0000
220@@ -271,6 +271,12 @@
221 class="lp.bugs.browser.bug.DeprecatedAssignedBugsView"
222 attribute="__call__"
223 permission="launchpad.AnyPerson"/>
224+ <adapter
225+ name="bugs"
226+ provides="lp.services.webapp.interfaces.IBreadcrumb"
227+ for="lp.registry.interfaces.person.IPerson"
228+ factory="lp.bugs.browser.bugtarget.BugsVHostBreadcrumb"
229+ permission="zope.Public"/>
230 <browser:page
231 for="lp.registry.interfaces.person.IPerson"
232 name="+team-bugs-macro"
233
234=== modified file 'lib/lp/bugs/browser/structuralsubscription.py'
235--- lib/lp/bugs/browser/structuralsubscription.py 2012-01-01 02:58:52 +0000
236+++ lib/lp/bugs/browser/structuralsubscription.py 2012-02-23 16:31:19 +0000
237@@ -95,17 +95,13 @@
238 custom_widget('subscriptions_team', LabeledMultiCheckBoxWidget)
239 custom_widget('remove_other_subscriptions', LabeledMultiCheckBoxWidget)
240
241- override_title_breadcrumbs = True
242+ page_title = 'Subscribe'
243
244 @property
245- def page_title(self):
246+ def label(self):
247 return 'Subscribe to Bugs in %s' % self.context.title
248
249 @property
250- def label(self):
251- return self.page_title
252-
253- @property
254 def next_url(self):
255 return canonical_url(self.context)
256
257
258=== modified file 'lib/lp/bugs/browser/tests/test_breadcrumbs.py'
259--- lib/lp/bugs/browser/tests/test_breadcrumbs.py 2012-01-01 02:58:52 +0000
260+++ lib/lp/bugs/browser/tests/test_breadcrumbs.py 2012-02-23 16:31:19 +0000
261@@ -17,7 +17,7 @@
262 super(TestBugTaskBreadcrumb, self).setUp()
263 product = self.factory.makeProduct(
264 name='crumb-tester', displayname="Crumb Tester")
265- self.bug = self.factory.makeBug(product=product)
266+ self.bug = self.factory.makeBug(product=product, title='borked')
267 self.bugtask = self.bug.default_bugtask
268 self.bugtask_url = canonical_url(self.bugtask, rootsite='bugs')
269
270@@ -26,6 +26,8 @@
271 last_crumb = crumbs[-1]
272 self.assertEquals(self.bugtask_url, last_crumb.url)
273 self.assertEquals("Bug #%d" % self.bug.id, last_crumb.text)
274+ self.assertEquals(
275+ u"Bug #%d \u201cborked\u201d" % self.bug.id, last_crumb.detail)
276
277 def test_bugtask_child(self):
278 crumbs = self.getBreadcrumbsForObject(
279@@ -77,3 +79,22 @@
280 (self.bug_tracker.title, self.bug_tracker_url),
281 ]
282 self.assertBreadcrumbs(expected_breadcrumbs, self.bug_tracker)
283+
284+
285+class BugsVHostBreadcrumbTestCase(BaseBreadcrumbTestCase):
286+
287+ def test_person(self):
288+ person = self.factory.makePerson(name='snarf')
289+ person_bugs_url = canonical_url(person, rootsite='bugs')
290+ crumbs = self.getBreadcrumbsForObject(person, rootsite='bugs')
291+ last_crumb = crumbs[-1]
292+ self.assertEquals(person_bugs_url, last_crumb.url)
293+ self.assertEquals("Bugs", last_crumb.text)
294+
295+ def test_bugtarget(self):
296+ project = self.factory.makeProduct(name='fnord')
297+ project_bugs_url = canonical_url(project, rootsite='bugs')
298+ crumbs = self.getBreadcrumbsForObject(project, rootsite='bugs')
299+ last_crumb = crumbs[-1]
300+ self.assertEquals(project_bugs_url, last_crumb.url)
301+ self.assertEquals("Bugs", last_crumb.text)
302
303=== modified file 'lib/lp/bugs/browser/tests/test_expose.py'
304--- lib/lp/bugs/browser/tests/test_expose.py 2012-01-01 02:58:52 +0000
305+++ lib/lp/bugs/browser/tests/test_expose.py 2012-02-23 16:31:19 +0000
306@@ -132,9 +132,12 @@
307 expose_user_administered_teams_to_js(self.request, self.user, context,
308 absoluteURL=fake_absoluteURL)
309 team_info = self._sort(self.request.objects['administratedTeams'])
310- self.assertThat(team_info[0]['title'], Equals(u'Team 1'))
311+ self.assertThat(
312+ team_info[0]['title'], Equals(u'\u201cTeam 1\u201d team'))
313 self.assertThat(team_info[0]['has_preferredemail'], Equals(False))
314- self.assertThat(team_info[1]['title'], Equals(u'Team 2'))
315+ self.assertThat(
316+ team_info[1]['title'],
317+ Equals(u'\u201cTeam 2\u201d team'))
318 self.assertThat(team_info[1]['has_preferredemail'], Equals(True))
319
320 def test_teams_for_non_distro(self):
321@@ -162,14 +165,16 @@
322 KeysEqual('has_preferredemail', 'link', 'title', 'url'))
323 # The link is the title of the team.
324 self.assertThat(
325- team_info[0]['title'], Equals(u'Bug Supervisor Sub Team'))
326- self.assertThat(
327- team_info[1]['title'], Equals(u'Bug Supervisor Team'))
328- self.assertThat(
329- team_info[2]['title'], Equals(u'Unrelated Team'))
330+ team_info[0]['title'],
331+ Equals(u'\u201cBug Supervisor Sub Team\u201d team'))
332+ self.assertThat(
333+ team_info[1]['title'],
334+ Equals(u'\u201cBug Supervisor Team\u201d team'))
335+ self.assertThat(
336+ team_info[2]['title'], Equals(u'\u201cUnrelated Team\u201d team'))
337 # The link is the API link to the team.
338 self.assertThat(team_info[0]['link'],
339- Equals('http://example.com/BugSupervisorSubTeam'))
340+ Equals(u'http://example.com/\u201cBugSupervisorSubTeam\u201dteam'))
341
342 def test_expose_user_administered_teams_to_js__uses_cached_teams(self):
343 # The function expose_user_administered_teams_to_js uses a
344@@ -236,12 +241,14 @@
345 KeysEqual('has_preferredemail', 'link', 'title', 'url'))
346 # The link is the title of the team.
347 self.assertThat(
348- team_info[0]['title'], Equals(u'Bug Supervisor Sub Team'))
349+ team_info[0]['title'],
350+ Equals(u'\u201cBug Supervisor Sub Team\u201d team'))
351 self.assertThat(
352- team_info[1]['title'], Equals(u'Bug Supervisor Team'))
353+ team_info[1]['title'],
354+ Equals(u'\u201cBug Supervisor Team\u201d team'))
355 # The link is the API link to the team.
356 self.assertThat(team_info[0]['link'],
357- Equals('http://example.com/BugSupervisorSubTeam'))
358+ Equals(u'http://example.com/\u201cBugSupervisorSubTeam\u201dteam'))
359
360 def test_teams_for_distro_with_no_bug_super(self):
361 self._setup_teams(self.user)
362@@ -266,14 +273,16 @@
363 KeysEqual('has_preferredemail', 'link', 'title', 'url'))
364 # The link is the title of the team.
365 self.assertThat(
366- team_info[0]['title'], Equals(u'Bug Supervisor Sub Team'))
367- self.assertThat(
368- team_info[1]['title'], Equals(u'Bug Supervisor Team'))
369- self.assertThat(
370- team_info[2]['title'], Equals(u'Unrelated Team'))
371+ team_info[0]['title'],
372+ Equals(u'\u201cBug Supervisor Sub Team\u201d team'))
373+ self.assertThat(
374+ team_info[1]['title'],
375+ Equals(u'\u201cBug Supervisor Team\u201d team'))
376+ self.assertThat(
377+ team_info[2]['title'], Equals(u'\u201cUnrelated Team\u201d team'))
378 # The link is the API link to the team.
379 self.assertThat(team_info[0]['link'],
380- Equals('http://example.com/BugSupervisorSubTeam'))
381+ Equals(u'http://example.com/\u201cBugSupervisorSubTeam\u201dteam'))
382
383
384 class TestStructuralSubscriptionHelpers(TestCase):
385
386=== modified file 'lib/lp/bugs/stories/bug-also-affects/xx-also-affects-new-upstream.txt'
387--- lib/lp/bugs/stories/bug-also-affects/xx-also-affects-new-upstream.txt 2011-12-22 05:09:10 +0000
388+++ lib/lp/bugs/stories/bug-also-affects/xx-also-affects-new-upstream.txt 2012-02-23 16:31:19 +0000
389@@ -19,7 +19,7 @@
390 We're now redirected to the newly created bugtask page.
391
392 >>> user_browser.title
393- 'Bug #1 in The Foo Project...'
394+ 'Bug #1 ... : Bugs : The Foo Project'
395
396 When creating a new upstream through this page we'll check if there's any
397 upstream already registered in Launchpad which uses the same bugtracker as
398@@ -61,7 +61,7 @@
399
400 >>> user_browser.getControl('Use Existing Project').click()
401 >>> user_browser.title
402- 'Bug #2 in The Foo Project...'
403+ 'Bug #2 (blackhole) ... : Bugs : The Foo Project'
404
405 >>> from lp.bugs.tests.bug import print_remote_bugtasks
406 >>> print_remote_bugtasks(user_browser.contents)
407@@ -86,7 +86,7 @@
408 ... 'http://bugs.foo.org/bugs/show_bug.cgi?id=123')
409 >>> user_browser.getControl('Continue').click()
410 >>> user_browser.title
411- 'Bug #2 in The Bar Project:...
412+ 'Bug #2 (blackhole) ... : Bugs : The Bar Project'
413 >>> print_remote_bugtasks(user_browser.contents)
414 The Bar Project ... auto-bugs.foo.org #123
415 The Bar Project ... auto-bugs.foo.org #421
416
417=== modified file 'lib/lp/bugs/stories/bugattachments/xx-attachments-to-bug-report.txt'
418--- lib/lp/bugs/stories/bugattachments/xx-attachments-to-bug-report.txt 2011-12-24 17:49:30 +0000
419+++ lib/lp/bugs/stories/bugattachments/xx-attachments-to-bug-report.txt 2012-02-23 16:31:19 +0000
420@@ -72,7 +72,7 @@
421 >>> logout()
422 >>> browser.open("http://bugs.launchpad.dev/redfish/+bug/11")
423 >>> print extract_text(browser.contents)
424- Bug #11 in Jokosher...
425+ Bug #11 ...
426 ...Patches...
427 ...a patch...
428 ...Bug attachments...
429
430=== modified file 'lib/lp/bugs/stories/bugs/xx-bug-comments-truncated.txt'
431--- lib/lp/bugs/stories/bugs/xx-bug-comments-truncated.txt 2012-02-01 15:26:32 +0000
432+++ lib/lp/bugs/stories/bugs/xx-bug-comments-truncated.txt 2012-02-23 16:31:19 +0000
433@@ -87,7 +87,7 @@
434
435 >>> user_browser.open('http://bugs.launchpad.dev/tomcat/+bug/2')
436 >>> user_browser.title.decode('utf-8')
437- u'Bug #2 in Tomcat: ...Blackhole Trash folder...'
438+ u'Bug #2 (blackhole) ... : Bugs : Tomcat'
439 >>> user_browser.getControl(name='field.comment').value = (
440 ... "-----BEGIN PGP SIGNED MESSAGE-----\n"
441 ... "Hash: SHA1\n"
442@@ -120,7 +120,7 @@
443 email addresses in messages.
444
445 >>> user_browser.title.decode('utf-8')
446- u'Bug #2 in Tomcat: ...Blackhole Trash folder...'
447+ u'Bug #2 (blackhole) ... : Bugs : Tomcat'
448 >>> text = find_tags_by_class(
449 ... user_browser.contents, 'boardCommentBody')[-1]
450 >>> print extract_text(text.findAll('p')[-2])
451@@ -135,7 +135,7 @@
452
453 >>> anon_browser.open('http://bugs.launchpad.dev/tomcat/+bug/2')
454 >>> anon_browser.title.decode('utf-8')
455- u'Bug #2 in Tomcat: ...Blackhole Trash folder...'
456+ u'Bug #2 (blackhole) ... : Bugs : Tomcat'
457 >>> text = find_tags_by_class(
458 ... anon_browser.contents, 'boardCommentBody')[-1]
459 >>> print extract_text(text.findAll('p')[-2])
460
461=== modified file 'lib/lp/bugs/stories/bugs/xx-bug-create-question.txt'
462--- lib/lp/bugs/stories/bugs/xx-bug-create-question.txt 2011-12-04 03:58:11 +0000
463+++ lib/lp/bugs/stories/bugs/xx-bug-create-question.txt 2012-02-23 16:31:19 +0000
464@@ -14,7 +14,7 @@
465 ... 'http://bugs.launchpad.dev'
466 ... '/ubuntu/+source/linux-source-2.6.15/+bug/10')
467 >>> anon_browser.title
468- 'Bug #10 in linux-source-2.6.15 (Ubuntu): ...another test bug...'
469+ 'Bug #10 ... : Bugs : ...linux-source-2.6.15... package : Ubuntu'
470
471 >>> anon_browser.getLink('Convert to a question').click()
472 Traceback (most recent call last):
473@@ -28,7 +28,7 @@
474 ... 'http://bugs.launchpad.dev'
475 ... '/ubuntu/+source/linux-source-2.6.15/+bug/10')
476 >>> user_browser.title
477- 'Bug #10 in linux-source-2.6.15 (Ubuntu): ...another test bug...'
478+ 'Bug #10 ... : Bugs : ...linux-source-2.6.15... package : Ubuntu'
479
480 >>> user_browser.getLink('Convert to a question').click()
481 >>> user_browser.title
482@@ -50,7 +50,7 @@
483 informational message stating that a question was created from the bug.
484
485 >>> user_browser.title
486- 'Bug #10 in linux-source-2.6.15 (Ubuntu): ...another test bug...'
487+ 'Bug #10 ... : Bugs : ...linux-source-2.6.15... package : Ubuntu'
488
489 >>> content = find_main_content(user_browser.contents)
490 >>> content.find(id="bug-is-question")
491@@ -114,7 +114,7 @@
492
493 >>> user_browser.getLink('#10: another test bug').click()
494 >>> user_browser.title
495- 'Bug #10 in linux-source-2.6.15 (Ubuntu): ...another test bug...'
496+ 'Bug #10 ... : Bugs : ...linux-source-2.6.15... package : Ubuntu'
497
498
499 When a question cannot be created from a bug
500@@ -127,8 +127,7 @@
501
502 >>> user_browser.open('http://bugs.launchpad.dev/thunderbird/+bug/9')
503 >>> user_browser.title
504- 'Bug #9 in Mozilla Thunderbird: \xe2\x80\x9cThunderbird
505- crashes\xe2\x80\x9d'
506+ 'Bug #9 ...'
507
508 >>> user_browser.getLink('Convert to a question').click()
509 >>> print user_browser.title
510@@ -191,13 +190,13 @@
511 >>> user_browser.open(
512 ... 'http://bugs.launchpad.dev/jokosher/+bug/12')
513 >>> user_browser.title
514- 'Bug #12 in Jokosher: ...Copy, Cut and Delete operations should work...'
515+ 'Bug #12 ...'
516
517 >>> user_browser.getLink('Convert to a question').click()
518 >>> user_browser.getControl('Comment').value = 'This will succeed.'
519 >>> user_browser.getControl('Convert this Bug into a Question').click()
520 >>> user_browser.title
521- 'Bug #12 in Jokosher: ...Copy, Cut and Delete operations should work...'
522+ 'Bug #12 ...'
523
524 >>> print "\n".join(get_feedback_messages(user_browser.contents))
525 This bug report was converted into a question:...question #16...
526@@ -219,8 +218,7 @@
527 reactivate a bug report.
528
529 >>> user_browser.title
530- 'Bug #12 in Jokosher: \xe2\x80\x9cCopy, Cut and Delete operations should
531- work on selections\xe2\x80\x9d'
532+ 'Bug #12 ... : Bugs : Jokosher'
533
534 >>> user_browser.getLink('Convert back to a bug').click()
535 >>> print user_browser.title
536@@ -244,7 +242,7 @@
537 the Open status.
538
539 >>> user_browser.title
540- 'Bug #12 in Jokosher: ...Copy, Cut and Delete operations should work...'
541+ 'Bug #12 ... : Bugs : Jokosher'
542
543 >>> print "\n".join(get_feedback_messages(user_browser.contents))
544 Removed Question #...: Copy, Cut and Delete operations should work...
545
546=== modified file 'lib/lp/bugs/stories/bugs/xx-bug-obfuscation.txt'
547--- lib/lp/bugs/stories/bugs/xx-bug-obfuscation.txt 2011-10-16 08:16:47 +0000
548+++ lib/lp/bugs/stories/bugs/xx-bug-obfuscation.txt 2012-02-23 16:31:19 +0000
549@@ -13,7 +13,7 @@
550 ... 'http://bugs.launchpad.dev'
551 ... '/debian/sarge/+source/mozilla-firefox/+bug/3')
552 >>> user_browser.title
553- 'Bug #3 in mozilla-firefox (Debian Sarge): ...Bug Title Test...'
554+ 'Bug #3 ...'
555
556 >>> description = find_tag_by_id(
557 ... user_browser.contents, 'edit-description')
558@@ -26,7 +26,7 @@
559 ... 'http://bugs.launchpad.dev'
560 ... '/debian/sarge/+source/mozilla-firefox/+bug/3')
561 >>> print anon_browser.title
562- Bug #3 in mozilla-firefox (Debian Sarge): ...
563+ Bug #3 ...
564
565 >>> 'user@domain.org' in anon_browser.contents
566 False
567
568=== modified file 'lib/lp/bugs/stories/bugtask-searches/xx-advanced-upstream-pending-bugwatch.txt'
569--- lib/lp/bugs/stories/bugtask-searches/xx-advanced-upstream-pending-bugwatch.txt 2012-02-08 00:47:42 +0000
570+++ lib/lp/bugs/stories/bugtask-searches/xx-advanced-upstream-pending-bugwatch.txt 2012-02-23 16:31:19 +0000
571@@ -31,7 +31,7 @@
572 ... 'I just want to register that it is upstream').selected = True
573 >>> browser.getControl('Add to Bug Report').click()
574 >>> browser.title
575- 'Bug #... in alsa-utils: ...Test Bug 1...'
576+ 'Bug #... : Bugs : alsa-utils'
577
578 Sample Person visits the advanced search page for alsa-utils, and
579 chooses to search for all bugs that need to be forwarded upstream.
580
581=== modified file 'lib/lp/bugs/stories/bugtask-searches/xx-person-bugs.txt'
582--- lib/lp/bugs/stories/bugtask-searches/xx-person-bugs.txt 2012-02-17 21:00:21 +0000
583+++ lib/lp/bugs/stories/bugtask-searches/xx-person-bugs.txt 2012-02-23 16:31:19 +0000
584@@ -7,7 +7,7 @@
585 >>> anon_browser.open('http://launchpad.dev/~name12')
586 >>> anon_browser.getLink('Bugs').click()
587 >>> print anon_browser.title
588- Related bugs
589+ Bugs : Sample Person
590
591 >>> print anon_browser.url
592 http://bugs.launchpad.dev/~name12
593@@ -49,7 +49,7 @@
594
595 >>> anon_browser.open('http://bugs.launchpad.dev/~name12')
596 >>> print anon_browser.title
597- Related bugs
598+ Bugs : Sample Person
599
600
601 More specific listings
602@@ -64,7 +64,7 @@
603
604 >>> anon_browser.getLink('Assigned bugs').click()
605 >>> print anon_browser.title
606- Assigned bugs : Sample Person
607+ Assigned bugs : Bugs : Sample Person
608
609 >>> print anon_browser.url
610 http://bugs.launchpad.dev/~name12/+assignedbugs
611@@ -83,7 +83,7 @@
612
613 >>> anon_browser.getLink('Commented bugs').click()
614 >>> print anon_browser.title
615- Commented bugs : Sample Person
616+ Commented bugs : Bugs : Sample Person
617
618 >>> print anon_browser.url
619 http://bugs.launchpad.dev/~name12/+commentedbugs
620@@ -106,7 +106,7 @@
621
622 >>> anon_browser.getLink('Reported bugs').click()
623 >>> print anon_browser.title
624- Reported bugs : Sample Person
625+ Reported bugs : Bugs : Sample Person
626
627 >>> print anon_browser.url
628 http://bugs.launchpad.dev/~name12/+reportedbugs
629@@ -137,7 +137,7 @@
630
631 >>> anon_browser.getLink('Subscribed bugs').click()
632 >>> print anon_browser.title
633- Subscribed bugs : Sample Person
634+ Subscribed bugs : Bugs : Sample Person
635
636 >>> print anon_browser.url
637 http://bugs.launchpad.dev/~name12/+subscribedbugs
638
639=== modified file 'lib/lp/bugs/stories/bugtracker/xx-bugtracker.txt'
640--- lib/lp/bugs/stories/bugtracker/xx-bugtracker.txt 2011-11-28 00:35:15 +0000
641+++ lib/lp/bugs/stories/bugtracker/xx-bugtracker.txt 2012-02-23 16:31:19 +0000
642@@ -424,8 +424,8 @@
643 Please note, this bug tracker cannot be deleted because:
644 This is the bug tracker for The GNOME Project and The GNOME Terminal
645 Emulator.
646- There are linked bug watches and only members of Launchpad
647- Administrators and Registry Administrators can delete them en masse.
648+ There are linked bug watches and only members of ...Launchpad
649+ Administrators...
650
651 >>> user_browser.getControl('Delete')
652 Traceback (most recent call last):
653@@ -448,10 +448,8 @@
654 >>> print extract_text(find_tag_by_id(
655 ... user_browser.contents, 'bugtracker-delete-not-possible-reasons'))
656 Please note, this bug tracker cannot be deleted because:
657- There are linked bug watches and only members of Launchpad
658- Administrators and Registry Administrators can delete them en masse.
659- Bug comments have been imported via this bug tracker.
660- This bug tracker is protected from deletion.
661+ There are linked bug watches and only members of ...Launchpad
662+ Administrators...
663
664 >>> user_browser.getControl('Delete')
665 Traceback (most recent call last):
666
667=== modified file 'lib/lp/bugs/stories/guided-filebug/xx-filebug-attachments.txt'
668--- lib/lp/bugs/stories/guided-filebug/xx-filebug-attachments.txt 2011-04-20 14:56:23 +0000
669+++ lib/lp/bugs/stories/guided-filebug/xx-filebug-attachments.txt 2012-02-23 16:31:19 +0000
670@@ -30,7 +30,7 @@
671 ... "of the attachment")
672 >>> user_browser.getControl('Submit Bug Report').click()
673 >>> user_browser.title
674- 'Bug #... in Mozilla Firefox...'
675+ 'Bug #... : Bugs : Mozilla Firefox'
676
677 No Privileges Person sees a notice on the bug page stating that the file
678 was attached.
679
680=== modified file 'lib/lp/bugs/stories/guided-filebug/xx-product-guided-filebug.txt'
681--- lib/lp/bugs/stories/guided-filebug/xx-product-guided-filebug.txt 2011-05-16 01:53:42 +0000
682+++ lib/lp/bugs/stories/guided-filebug/xx-product-guided-filebug.txt 2012-02-23 16:31:19 +0000
683@@ -165,7 +165,5 @@
684 >>> user_browser.url
685 'http://bugs.launchpad.dev/firefox/+bug/...'
686
687- >>> user_browser.title
688- "Bug #... in Mozilla Firefox: ...Frankenzombulon reanimated..."
689-
690-
691+ >>> print user_browser.title
692+ Bug #...Frankenzombulon reanimated... : Bugs : Mozilla Firefox
693
694=== modified file 'lib/lp/bugs/stories/guided-filebug/xx-project-guided-filebug.txt'
695--- lib/lp/bugs/stories/guided-filebug/xx-project-guided-filebug.txt 2011-05-27 19:53:20 +0000
696+++ lib/lp/bugs/stories/guided-filebug/xx-project-guided-filebug.txt 2012-02-23 16:31:19 +0000
697@@ -37,7 +37,7 @@
698 'http://bugs.launchpad.dev/evolution/+bug/...'
699
700 >>> user_browser.title
701- 'Bug #... in Evolution: ...Evolution crashes...'
702+ 'Bug #...Evolution crashes... : Bugs : Evolution'
703
704
705 Subscribing to a similar bug
706@@ -109,7 +109,7 @@
707 'http://bugs.launchpad.dev/evolution/+bug/...'
708
709 >>> user_browser.title
710- 'Bug #... in Evolution: ...Faznambutron dumps core...'
711+ 'Bug #...Faznambutron dumps core... : Bugs : Evolution'
712
713
714 Empty ProjectGroups
715@@ -281,5 +281,3 @@
716 >>> user_browser.getLink('File a bug here').click()
717 >>> user_browser.url
718 'http://launchpad.dev/ubuntu/+source/thunderbird/+filebug'
719-
720-
721
722=== modified file 'lib/lp/code/stories/branches/xx-branch-index.txt'
723--- lib/lp/code/stories/branches/xx-branch-index.txt 2011-12-30 06:14:56 +0000
724+++ lib/lp/code/stories/branches/xx-branch-index.txt 2012-02-23 16:31:19 +0000
725@@ -112,7 +112,7 @@
726
727 >>> browser.getLink('bug 1').click()
728 >>> print browser.title
729- Bug #1 in Mozilla Firefox...
730+ Bug #1 ...
731
732
733 Unscanned branches
734
735=== modified file 'lib/lp/hardwaredb/stories/hwdb/xx-hwdb.txt'
736--- lib/lp/hardwaredb/stories/hwdb/xx-hwdb.txt 2012-01-15 11:06:57 +0000
737+++ lib/lp/hardwaredb/stories/hwdb/xx-hwdb.txt 2012-02-23 16:31:19 +0000
738@@ -350,7 +350,7 @@
739 >>> anon_browser.open(
740 ... 'http://launchpad.dev/~ubuntu-team/+hwdb-submissions')
741 >>> print extract_text(find_main_content(anon_browser.contents))
742- Hardware submissions for Ubuntu Team...
743+ Hardware submissions for ...Ubuntu Team...
744 Ubuntu Team has posted no submissions.
745
746 >>> browser = setupBrowser(auth='Basic celso.providelo@canonical.com:test')
747
748=== modified file 'lib/lp/registry/model/person.py'
749--- lib/lp/registry/model/person.py 2012-02-21 22:46:28 +0000
750+++ lib/lp/registry/model/person.py 2012-02-23 16:31:19 +0000
751@@ -40,7 +40,11 @@
752 import weakref
753
754 from lazr.delegates import delegates
755-from lazr.restful.utils import get_current_browser_request
756+from lazr.restful.utils import (
757+ get_current_browser_request,
758+ smartquote,
759+ )
760+
761 import pytz
762 from sqlobject import (
763 BoolCol,
764@@ -1777,6 +1781,8 @@
765 @property
766 def title(self):
767 """See `IPerson`."""
768+ if self.is_team:
769+ return smartquote('"%s" team') % self.displayname
770 return self.displayname
771
772 @property
773
774=== modified file 'lib/lp/registry/stories/distroseries/xx-distroseries-index.txt'
775--- lib/lp/registry/stories/distroseries/xx-distroseries-index.txt 2011-08-16 15:24:16 +0000
776+++ lib/lp/registry/stories/distroseries/xx-distroseries-index.txt 2012-02-23 16:31:19 +0000
777@@ -125,7 +125,7 @@
778 http://launchpad.dev/ubuntu/warty/+subscribe
779
780 >>> print admin_browser.title
781- Subscribe to Bugs in The Warty Warthog Release
782+ Subscribe : Warty (4.10) : Ubuntu
783
784
785 Upstream packaging portlet
786
787=== modified file 'lib/lp/registry/stories/milestone/object-milestones.txt'
788--- lib/lp/registry/stories/milestone/object-milestones.txt 2012-02-21 22:46:28 +0000
789+++ lib/lp/registry/stories/milestone/object-milestones.txt 2012-02-23 16:31:19 +0000
790@@ -191,14 +191,11 @@
791 Main heading: Debian 3.1
792
793
794-Duplicate bugs
795-..............
796-
797-Milestone bug listings do not show bugs that are marked as duplicates of
798-other bugs.
799-
800-To demonstrate this, we'll begin by filing a couple of bugs for the
801-Mozilla Firefox product:
802+
803+Bugs targeted to multiple series
804+................................
805+
806+Setup this embarrasing story.
807
808 >>> browser = setupBrowser(auth='Basic test@canonical.com:test')
809 >>> browser.open('http://bugs.launchpad.dev/firefox/')
810@@ -247,81 +244,6 @@
811 >>> browser.getControl('Milestone').value = [milestone_id]
812 >>> browser.getControl('Save Changes').click()
813
814-Observe that both bugs are listed in the 1.0 milestone listing:
815-
816- >>> browser.open('http://launchpad.dev/firefox/trunk')
817- >>> browser.getLink('View milestones').click()
818- >>> browser.getLink("Mozilla Firefox 1.0", index=1).click()
819- >>> print browser.title
820- 1.0 : Mozilla Firefox
821-
822- >>> milestone_url = browser.url
823-
824- >>> browser.getLink('Test Bug 1').click()
825- >>> browser.url == bug_1_url
826- True
827-
828- >>> browser.open(milestone_url)
829- >>> browser.getLink('Test Bug 2').click()
830- >>> browser.url == bug_2_url
831- True
832-
833-Now we'll mark the second bug as a duplicate of the first:
834-
835- >>> browser.open(bug_2_url)
836- >>> browser.getLink('Mark as duplicate').click()
837- >>> browser.getControl('Duplicate').value = bug_1_id
838- >>> browser.getControl('Change').click()
839- >>> print extract_text(browser.contents)
840- Bug #20 in Mozilla Firefox...
841- Duplicate of bug #19...
842-
843-Since duplicate bugs are not listed in milestone listings, only our
844-first bug is listed in the 1.0 milestone listing:
845-
846- >>> from lp.testing.layers import MemcachedLayer
847-
848- >>> MemcachedLayer.purge()
849-
850- >>> browser.open(milestone_url)
851- >>> browser.getLink('Test Bug 1').click()
852- >>> browser.url == bug_1_url
853- True
854-
855- >>> browser.open(milestone_url)
856- >>> print browser.getLink('Test Bug 2')
857- Traceback (most recent call last):
858- ...
859- LinkNotFoundError
860-
861-However, it's also possible to clear the duplicate status of our second
862-bug:
863-
864- >>> browser.open(bug_2_url)
865- >>> browser.getLink(id='change_duplicate_bug').click()
866- >>> browser.getControl('Duplicate').value = ''
867- >>> browser.getControl('Change').click()
868- >>> 'This report is a duplicate' in find_main_content(browser.contents)
869- False
870-
871-Now both bugs are listed in the 1.0 milestone listing once again:
872-
873- >>> MemcachedLayer.purge()
874-
875- >>> browser.open(milestone_url)
876- >>> browser.getLink('Test Bug 1').click()
877- >>> browser.url == bug_1_url
878- True
879-
880- >>> browser.open(milestone_url)
881- >>> browser.getLink('Test Bug 2').click()
882- >>> browser.url == bug_2_url
883- True
884-
885-
886-Bugs targeted to multiple series
887-................................
888-
889 Bugs targeted to the same milestone across more than one series will
890 result in duplicate entries in the milestone listing (one for each
891 series target).
892@@ -357,6 +279,7 @@
893 >>> print browser.title
894 Series trunk : Mozilla Firefox
895
896+ >>> from lp.testing.layers import MemcachedLayer
897 >>> MemcachedLayer.purge()
898 >>> browser.open('http://launchpad.dev/firefox/trunk')
899
900@@ -377,11 +300,11 @@
901 >>> from lp.services.helpers import backslashreplace
902 >>> browser.open(bug_1_url)
903 >>> print backslashreplace(browser.title)
904- Bug...in Mozilla Firefox...
905+ Bug #...Test Bug 1... : Bugs : Mozilla Firefox
906
907 >>> browser.getLink('Target to series').click()
908 >>> print browser.title
909- Target bug #19 to series...
910+ Target bug #... to series...
911
912 >>> browser.getControl('1.0').selected = True
913 >>> browser.getControl('2.0').selected = True
914
915=== modified file 'lib/lp/registry/stories/milestone/xx-milestone-add-and-edit.txt'
916--- lib/lp/registry/stories/milestone/xx-milestone-add-and-edit.txt 2011-12-28 17:03:06 +0000
917+++ lib/lp/registry/stories/milestone/xx-milestone-add-and-edit.txt 2012-02-23 16:31:19 +0000
918@@ -73,7 +73,7 @@
919 >>> print user_browser.url
920 http://launchpad.dev/firefox/+milestone/1.0/+subscribe
921 >>> print user_browser.title
922- Subscribe to Bugs in Mozilla Firefox 1.0
923+ Subscribe : 1.0 : Mozilla Firefox
924
925 But we can't subscribe to project milestones, since they are not real objects.
926
927
928=== modified file 'lib/lp/registry/stories/person/xx-person-subscriptions.txt'
929--- lib/lp/registry/stories/person/xx-person-subscriptions.txt 2012-01-15 13:32:27 +0000
930+++ lib/lp/registry/stories/person/xx-person-subscriptions.txt 2012-02-23 16:31:19 +0000
931@@ -77,7 +77,7 @@
932 >>> cancel_link = subscriber_browser.getLink('Cancel')
933 >>> cancel_link.click()
934 >>> print subscriber_browser.title
935- Subscriptions : Webster
936+ Subscriptions : Bugs : Webster
937
938 He chooses to unsubscribe from the bug about Affluenza.
939
940@@ -87,7 +87,7 @@
941 ... "unsubscribe me from this bug").selected = True
942 >>> subscriber_browser.getControl("Continue").click()
943 >>> print subscriber_browser.title
944- Subscriptions : Webster
945+ Subscriptions : Bugs : Webster
946
947 Webster can see that the bug about Affluenza is no longer listed in his direct
948 bug subscriptions.
949@@ -118,7 +118,7 @@
950 >>> subscriber_browser.open(
951 ... 'https://bugs.launchpad.dev/~america/+subscriptions')
952 >>> print subscriber_browser.title
953- Subscriptions : “America” team
954+ Subscriptions : Bugs ...
955
956 >>> print extract_text(find_tag_by_id(
957 ... subscriber_browser.contents, 'bug_subscriptions'))
958
959=== modified file 'lib/lp/registry/stories/productseries/xx-productseries-add-and-edit.txt'
960--- lib/lp/registry/stories/productseries/xx-productseries-add-and-edit.txt 2009-09-17 22:27:18 +0000
961+++ lib/lp/registry/stories/productseries/xx-productseries-add-and-edit.txt 2012-02-23 16:31:19 +0000
962@@ -126,4 +126,4 @@
963 >>> print browser.url
964 http://launchpad.dev/firefox/unstable/+subscribe
965 >>> print browser.title
966- Subscribe to Bugs in Mozilla Firefox unstable series
967+ Subscribe : Series unstable : Mozilla Firefox
968
969=== modified file 'lib/lp/registry/stories/project/xx-project-index.txt'
970--- lib/lp/registry/stories/project/xx-project-index.txt 2012-01-04 12:11:44 +0000
971+++ lib/lp/registry/stories/project/xx-project-index.txt 2012-02-23 16:31:19 +0000
972@@ -184,4 +184,4 @@
973 >>> print user_browser.url
974 http://launchpad.dev/mozilla/+subscribe
975 >>> print user_browser.title
976- Subscribe to Bugs in The Mozilla Project
977+ Subscribe : The Mozilla Project
978
979=== modified file 'lib/lp/registry/tests/test_person.py'
980--- lib/lp/registry/tests/test_person.py 2012-02-19 14:44:47 +0000
981+++ lib/lp/registry/tests/test_person.py 2012-02-23 16:31:19 +0000
982@@ -6,6 +6,8 @@
983 from datetime import datetime
984
985 from lazr.lifecycle.snapshot import Snapshot
986+from lazr.restful.utils import smartquote
987+
988 import pytz
989 from storm.store import Store
990 from testtools.matchers import (
991@@ -261,6 +263,16 @@
992
993 layer = DatabaseFunctionalLayer
994
995+ def test_title_user(self):
996+ user = self.factory.makePerson(name='snarf')
997+ self.assertEqual('Snarf', user.title)
998+ self.assertEqual(user.displayname, user.title)
999+
1000+ def test_title_team(self):
1001+ team = self.factory.makeTeam(name='pting')
1002+ title = smartquote('"%s" team') % team.displayname
1003+ self.assertEqual(title, team.title)
1004+
1005 def test_getOwnedOrDrivenPillars(self):
1006 user = self.factory.makePerson()
1007 active_project = self.factory.makeProject(owner=user)
1008
1009=== modified file 'lib/lp/services/webapp/breadcrumb.py'
1010--- lib/lp/services/webapp/breadcrumb.py 2011-12-24 17:49:30 +0000
1011+++ lib/lp/services/webapp/breadcrumb.py 2012-02-23 16:31:19 +0000
1012@@ -30,6 +30,7 @@
1013 implements(IBreadcrumb)
1014
1015 text = None
1016+ _detail = None
1017 _url = None
1018
1019 def __init__(self, context):
1020@@ -55,9 +56,18 @@
1021 else:
1022 return self._url
1023
1024+ @property
1025+ def detail(self):
1026+ """See `IBreadcrumb`.
1027+
1028+ Subclasses may choose to provide detail text that will be used
1029+ to make the page title for the last item traversed.
1030+ """
1031+ return self._detail or self.text
1032+
1033 def __repr__(self):
1034 # XXX: salgado, 2009-10-14, http://bugs.python.org/issue5876: In
1035- # python 2.5, the return value of __repr__() may be forced into a
1036+ # python 2.5-2.7, the return value of __repr__() may be forced into a
1037 # type(str), so we can't include unicode here.
1038 text = self.text.encode('raw-unicode-escape')
1039 return "<%s url='%s' text='%s'>" % (
1040
1041=== modified file 'lib/lp/services/webapp/error.py'
1042--- lib/lp/services/webapp/error.py 2012-01-23 21:23:24 +0000
1043+++ lib/lp/services/webapp/error.py 2012-02-23 16:31:19 +0000
1044@@ -41,7 +41,6 @@
1045 implements(ISystemErrorView)
1046
1047 page_title = 'Error: Launchpad system error'
1048- override_title_breadcrumbs = True
1049
1050 plain_oops_template = ViewPageTemplateFile(
1051 'templates/oops-veryplain.pt')
1052@@ -185,7 +184,6 @@
1053 class NotFoundView(SystemErrorView):
1054
1055 page_title = 'Error: Page not found'
1056- override_title_breadcrumbs = True
1057
1058 response_code = httplib.NOT_FOUND
1059
1060@@ -224,7 +222,6 @@
1061 class RequestExpiredView(SystemErrorView):
1062
1063 page_title = 'Error: Timeout'
1064- override_title_breadcrumbs = True
1065
1066 response_code = httplib.SERVICE_UNAVAILABLE
1067
1068@@ -240,7 +237,6 @@
1069 """View rendered when an InvalidBatchSizeError is raised."""
1070
1071 page_title = "Error: Invalid Batch Size"
1072- override_title_breadcrumbs = True
1073
1074 response_code = httplib.BAD_REQUEST
1075
1076@@ -259,7 +255,6 @@
1077 class TranslationUnavailableView(SystemErrorView):
1078
1079 page_title = 'Error: Translation page is not available'
1080- override_title_breadcrumbs = True
1081
1082 response_code = httplib.SERVICE_UNAVAILABLE
1083
1084@@ -271,7 +266,6 @@
1085 """View rendered when an InvalidBatchSizeError is raised."""
1086
1087 page_title = "Error: you can't do this right now"
1088- override_title_breadcrumbs = True
1089
1090 response_code = httplib.SERVICE_UNAVAILABLE
1091
1092
1093=== modified file 'lib/lp/services/webapp/interfaces.py'
1094--- lib/lp/services/webapp/interfaces.py 2012-02-21 22:46:28 +0000
1095+++ lib/lp/services/webapp/interfaces.py 2012-02-23 16:31:19 +0000
1096@@ -234,6 +234,8 @@
1097
1098 text = Attribute('Text of this breadcrumb.')
1099
1100+ detail = Attribute('Detailed text of this breadcrumb.')
1101+
1102
1103 #
1104 # Canonical URLs
1105
1106=== modified file 'lib/lp/services/webapp/tests/test_breadcrumbs.py'
1107--- lib/lp/services/webapp/tests/test_breadcrumbs.py 2012-01-01 02:58:52 +0000
1108+++ lib/lp/services/webapp/tests/test_breadcrumbs.py 2012-02-23 16:31:19 +0000
1109@@ -21,10 +21,39 @@
1110 class Cookbook:
1111 implements(ICanonicalUrlData)
1112 rootsite = None
1113+ path = 'cookbook'
1114+ inside = None
1115
1116
1117 class TestBreadcrumb(TestCase):
1118
1119+ def test_init(self):
1120+ # The attributes are None by default.
1121+ cookbook = Cookbook()
1122+ breadcrumb = Breadcrumb(cookbook)
1123+ self.assertIs(None, breadcrumb.text)
1124+ self.assertIs(None, breadcrumb._detail)
1125+ self.assertIs(None, breadcrumb._url)
1126+
1127+ def test_detail(self):
1128+ # The detail properted is the _detail attribute or the text attribute.
1129+ cookbook = Cookbook()
1130+ breadcrumb = Breadcrumb(cookbook)
1131+ breadcrumb._detail = 'hello'
1132+ breadcrumb.text = 'goodbye'
1133+ self.assertEqual('hello', breadcrumb.detail)
1134+ breadcrumb._detail = None
1135+ self.assertEqual('goodbye', breadcrumb.detail)
1136+
1137+ def test_url(self):
1138+ # The detail properted is the _detail attribute or the text attribute.
1139+ cookbook = Cookbook()
1140+ breadcrumb = Breadcrumb(cookbook)
1141+ breadcrumb._url = '/hello'
1142+ self.assertEqual('/hello', breadcrumb.url)
1143+ breadcrumb._url = None
1144+ self.assertEqual('http://launchpad.dev/cookbook', breadcrumb.url)
1145+
1146 def test_rootsite_defaults_to_mainsite(self):
1147 # When a class' ICanonicalUrlData doesn't define a rootsite, our
1148 # Breadcrumb adapter will use 'mainsite' as the rootsite.
1149@@ -123,7 +152,8 @@
1150
1151 def test_product_bugtask(self):
1152 self.assertBreadcrumbUrls(
1153- [self.product_url, self.product_bugs_url, self.product_bugtask_url],
1154+ [self.product_url, self.product_bugs_url,
1155+ self.product_bugtask_url],
1156 self.product_bugtask)
1157
1158 def test_package_bugtask(self):
1159
1160=== modified file 'lib/lp/translations/stories/standalone/xx-person-activity.txt'
1161--- lib/lp/translations/stories/standalone/xx-person-activity.txt 2010-12-10 12:14:52 +0000
1162+++ lib/lp/translations/stories/standalone/xx-person-activity.txt 2012-02-23 16:31:19 +0000
1163@@ -45,7 +45,7 @@
1164 ... 'Spanish (es) translation of alsa-utils in alsa-utils trunk')
1165 >>> anon_browser.getLink(alsa_utils_link).click()
1166 >>> print anon_browser.title
1167- Translations by ... in...Spanish (es) translation...
1168+ Translations by ...Spanish (es)...
1169
1170
1171 URL-escaped user names