Merge lp:~barry/launchpad/417089-headings into lp:launchpad

Proposed by Barry Warsaw
Status: Merged
Merged at revision: not available
Proposed branch: lp:~barry/launchpad/417089-headings
Merge into: lp:launchpad
Diff against target: None lines
To merge this branch: bzr merge lp:~barry/launchpad/417089-headings
Reviewer Review Type Date Requested Status
Guilherme Salgado (community) Approve
Review via email: mp+11210@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Barry Warsaw (barry) wrote :
Download full text (6.5 KiB)

 reviewer salgado

= Summary =

Bug 417089 addresses our current inconsistencies and difficulties with getting
the headings and page titles correct. After discussion with Martin A, we've
codified the rules here:

https://dev.launchpad.net/VersionThreeDotO/UI/Conversion#Heading%20rules

This section contains both the rules for what you should see on a page, and
how to code your views to conform. Both instructions probably could use for
some review as well to make sure I've gotten them right. I intend to do
another pass through these instructions to include actual examples of pages
and templates from our tree so folks can cargo cult into their own pages.

As I mentioned in IRC, the branch is not yet ready to land, but it's close. I
believe all the header/title rules are implemented, and I have also included a
fix for bug 423411, which converts the ~person/+karma page to the new heading
rules. However, ec2 is reporting test failures which I have to fix before I
land the branch. If necessary I may ask for a follow up review.

I also suck because this branch is currently at 1035 lines.

== Proposed fix ==

Update the base-layout.pt template and launchpad-form.pt, as well as all
supporting view code, interfaces, zcml files, and style sheets to support the
new rules. Update some of the tests (more to come). Fix the person +karma
pages to conform.

Add several interfaces used to mark views to tell the base layout code how to
render the headings, either as an H1, an H2, or as an editable widget.

== Pre-implementation notes ==

I had several discussions with Martin about the rules, you Salgado to help me
with a few things regarding breadcrumbs, and a few other people about
specifics with this branch.

== Implementation details ==

In addition to the above, I also:

 * Improved the way the Launchpad release number is calculated for the
   base-layout template. Now, to update that release number (e.g. 2.2.7) you
   just edit the version.txt file in the root of the tree.
 * Cleaned up some whitespace
 * Added a launchpad-page-title.pt template for rendering the reverse
   breadcrumb <title>
 * Added additional ZCML to correctly calculate the <title> for some
   bug/branch pages that were overriding breadcrumb calculation
 * moved the IRootContext interface and consolidated related interfaces
 * Refactored the +karma view support into a separate PersonKarmaView class
 * Redesigned the +karma page based on Martin's ui suggestions, including
   defining a new, narrower table.
 * Made the project index page title editable

== Tests ==

This test was fixed to show the new code to show how the view annotations work
for the new header rules, including for editable heading widgets.

 % bin/test -vv -t watermark.txt

More tests to come, once they pass ;)

== Demo and Q/A ==

These pages show some interesting things, such as the headers and breadcrumbs
all UI 3.0 -like.

 https://launchpad.dev/firefox
 https://launchpad.dev/people/+newteam
 https://launchpad.dev/~name16/+karma
 https://code.launchpad.dev/~mark/firefox/release-0.8/+merge/1/+review?claim=name16&review_type=

(you will need to create a merge proposal for the firefox 0.8 branch into the
trunk...

Read more...

Revision history for this message
Guilherme Salgado (salgado) wrote :
Download full text (16.1 KiB)

Hi Barry,

Thanks a lot for your work on defining the heading rules and
implementing them. I have a few suggestions below, and I'll send a
separate review for the changes on the karma page.

Also, the 404 page now has a title which doesn't reflect that.
<https://code.launchpad.dev/~mark/firefox/release-0.8/+merge/1111>

On Fri, 2009-09-04 at 15:35 +0000, Barry Warsaw wrote:
> === modified file 'lib/canonical/launchpad/icing/style-3-0.css'
> --- lib/canonical/launchpad/icing/style-3-0.css 2009-09-04 11:25:26 +0000
> +++ lib/canonical/launchpad/icing/style-3-0.css 2009-09-04 12:56:38 +0000
> @@ -681,6 +681,20 @@
> table.listing th {
> font-weight: bold;
> }
> +table.narrow-listing {
> + width: 45em;
> +}
> +/* ~person/+karma */
> +table.cozy-listing {
> + width: 20em;

I think the correct indentation for our CSS is 2 spaces, no?

> + background-color: #fff;
> + border: 1px solid #d2d2d2;
> + border-bottom: 1px solid #d2d2d2;
> +}
> +table.cozy-listing td {
> + border: 1px #d2d2d2;
> + border-style: dotted none none none;
> +}
>
> ul.language, li.language {
> list-style-image: url(/@@/language);
> @@ -743,7 +757,3 @@
> font-weight: normal;
> padding: 2px 1em 2px 2px;
> }
> -
> -table.narrow-listing {
> - width: 45em;
> -}
>

> === modified file 'lib/lp/app/browser/configure.zcml'
> --- lib/lp/app/browser/configure.zcml 2009-08-27 09:06:42 +0000
> +++ lib/lp/app/browser/configure.zcml 2009-09-03 19:12:20 +0000
> @@ -38,6 +38,15 @@
> permission="zope.Public"
> />
>
> + <!-- Page title reversed breadcrumbs -->
> + <browser:page
> + for="zope.interface.Interface"
> + name="+page-title"
> + class="canonical.launchpad.browser.launchpad.Hierarchy"
> + template="../templates/launchpad-page-title.pt"
> + permission="zope.Public"
> + />
> +
> <!-- TALES watermark: namespace -->
> <adapter
> for="*"
>
> === modified file 'lib/lp/app/browser/tests/watermark.txt'
> --- lib/lp/app/browser/tests/watermark.txt 2009-08-19 23:11:16 +0000
> +++ lib/lp/app/browser/tests/watermark.txt 2009-09-03 23:06:50 +0000
[...]
> +
> +Heading level
> +=============
> +
> +The watermark heading is shown above the application tabs, in either H1 level
> +or H2 level. The heading level is determined by the view. For the index view
> +of the context, H1 is used. For all non-index pages, i.e. subpages, H2 is
> +used.
> +
> +The choice of heading level is controlled by a marker interface on the view.
> +Normally, the view class does not implement the marker interface, meaning it
> +is not the index page of the context. In this case the heading is rendered in
> +H2.
> +
> + >>> print test_tales('view/watermark:heading', view=view(widget))
> + <h2>Widget</h2>
> +
> +If the view class implements IIndexView, then this is the index page for the
> +context and the heading is rendered in H1.
> +
> + >>> from zope.interface import implements
> + >>> from lp.app.interfaces.headings import IIndexView
> +
> + >>> class view:
> + ... implements(IIndexView)
> + ... def __init__(self, context):
> + ... s...

Revision history for this message
Guilherme Salgado (salgado) wrote :
Download full text (5.3 KiB)

Hi Barry,

The new +karma page looks much better. I have only a few minor changes
to suggest.

On Fri, 2009-09-04 at 15:35 +0000, Barry Warsaw wrote:
> === modified file 'lib/lp/registry/browser/person.py'
> --- lib/lp/registry/browser/person.py 2009-09-04 02:20:59 +0000
> +++ lib/lp/registry/browser/person.py 2009-09-04 12:56:38 +0000
> @@ -39,6 +39,7 @@
> 'PersonFacets',
> 'PersonGPGView',
> 'PersonIndexView',
> + 'PersonKarmaView',
> 'PersonLanguagesView',
> 'PersonLatestQuestionsView',
> 'PersonNavigation',
> @@ -2507,8 +2508,26 @@
> rootsite='answers')
>
>
> +class PersonKarmaView(LaunchpadView):
> + """A view class used for ~person/+karma."""
> +
> + @property
> + def label(self):
> + return 'Launchpad Karma for ' + cgi.escape(self.user.displayname)
> +
> + @cachedproperty
> + def has_karma(self):
> + """Does the have karma?"""

s/the have/the person have/?

> + return bool(self.context.karma_category_caches)
> +
> + @cachedproperty
> + def has_expired_karma(self):
> + """Did the user have karma?"""

s/user/person. User is the term we use to refer to the person who's
interacting with LP, and here we're talking about the context of the
view.

I know you just copied-and-pasted them, but these are small enough
changes that I'm sure you won't mind doing. ;)

> + return self.context.latestKarma().count() > 0
> +
> +
> class PersonView(LaunchpadView, FeedsMixin):
> - """A View class used in almost all Person's pages."""
> + """A view class used in almost all Person's pages."""
>
> @cachedproperty
> def recently_approved_members(self):
> @@ -2900,16 +2919,6 @@
> else:
> return getUtility(ILaunchpadCelebrities).english.englishname
>
> - @cachedproperty
> - def has_karma(self):
> - """Does the have karma?"""
> - return bool(self.context.karma_category_caches)
> -
> - @cachedproperty
> - def has_expired_karma(self):
> - """Did the user have karm?."""
> - return self.context.latestKarma().count() > 0
> -
> @property
> def public_private_css(self):
> """The CSS classes that represent the public or private state."""
>

> === modified file 'lib/lp/registry/templates/person-karma.pt'
> --- lib/lp/registry/templates/person-karma.pt 2009-07-17 17:59:07 +0000
> +++ lib/lp/registry/templates/person-karma.pt 2009-09-03 00:38:35 +0000
[...]
> -</body>
> + >
> + <body>
> + <div metal:fill-slot="main"
> + tal:define="latest_karma context/latestKarma">
> +
> + <p class="application-summary">
> + This is a summary of the Launchpad karma earned by
> + <span tal:replace="context/displayname" />, organized by activity
> + type.
> + </p>
> +
> + <p id="no-karma" tal:condition="not: view/has_karma">
> + <tal:expired condition="view/has_expired_karma">
> + <tal:name replace="context/displayname" />'s karma has expired.
> + </tal:expired>
> + <tal:never condition="not: view/has_expired_karma">
> + No karma has yet been assigned to
> + <tal:name replace="...

Read more...

Revision history for this message
Guilherme Salgado (salgado) :
review: Needs Fixing
Revision history for this message
Barry Warsaw (barry) wrote :
Download full text (20.2 KiB)

On Sep 04, 2009, at 06:42 PM, Guilherme Salgado wrote:

>Thanks a lot for your work on defining the heading rules and
>implementing them. I have a few suggestions below, and I'll send a
>separate review for the changes on the karma page.

Thanks for the review Salgado. I'm still working on getting all the tests to
pass, and OCR last week cut into my time on this branch, but I'm keen to
resolve the issues and get this landed so we have time to update our pages to
the new rules.

>Also, the 404 page now has a title which doesn't reflect that.
><https://code.launchpad.dev/~mark/firefox/release-0.8/+merge/1111>

Yep, there are no merge proposals in sample data, so you have to create this
manually.

>
>On Fri, 2009-09-04 at 15:35 +0000, Barry Warsaw wrote:
>> === modified file 'lib/canonical/launchpad/icing/style-3-0.css'
>> --- lib/canonical/launchpad/icing/style-3-0.css 2009-09-04 11:25:26 +0000
>> +++ lib/canonical/launchpad/icing/style-3-0.css 2009-09-04 12:56:38 +0000
>> @@ -681,6 +681,20 @@
>> table.listing th {
>> font-weight: bold;
>> }
>> +table.narrow-listing {
>> + width: 45em;
>> +}
>> +/* ~person/+karma */
>> +table.cozy-listing {
>> + width: 20em;
>
>I think the correct indentation for our CSS is 2 spaces, no?

style-3-0.css is pretty inconsistent here, but it looks to me like most of the
styles use 4 space indents. I'd prefer that (for consistency) and will put
this on the agenda for this week's reviewer meetings.

>> +Editable headings
>> +=================
>> +
>> +Some index pages also allow for editing the context's description. To specify
>
>s/description/title?

Yep, thanks!

>> def heading(self):
>> - """Return the heading text for the root context.
>> -
>> - If the context itself provides IRootContext then we return an H1,
>> - otherwise it is a H2.
>> + """Return the heading text for the page.
>> +
>> + If the view provides `IEditableContextTitle` then the top heading is
>> + rendered from the view's `title_edit_widget` and is generally
>> + editable.
>> +
>> + Otherwise, if the context provides `IRootContext` then we return an
>> + H1, else an H2.
>> """
>> - if IRootContext.providedBy(self._context):
>> + # Check the view; is the title editable?
>> + if IEditableContextTitle.providedBy(self._view):
>> + return self._view.title_edit_widget()
>> + # The title is static, but only the root context gets an H1.
>
>s/the root context/the context's index view/?

Fixed.

>> === renamed file 'lib/lp/app/interfaces/rootcontext.py' => 'lib/lp/app/interfaces/headings.py'
>> --- lib/lp/app/interfaces/rootcontext.py 2009-08-19 06:18:41 +0000
>> +++ lib/lp/app/interfaces/headings.py 2009-09-04 12:59:09 +0000
>> @@ -1,10 +1,12 @@
>> # Copyright 2009 Canonical Ltd. This software is licensed under the
>> # GNU Affero General Public License version 3 (see the file LICENSE).
>>
>> -"""The primary context interface."""
>> +"""Interfaces for headings and breadcrumbs."""
>
>Actually, there's nothing here for breadcrumbs, is there?
>
>I think these are more like
>
> """Marker interfaces that define how to...

Revision history for this message
Barry Warsaw (barry) wrote :
Download full text (5.1 KiB)

On Sep 04, 2009, at 07:30 PM, Guilherme Salgado wrote:

>The new +karma page looks much better. I have only a few minor changes
>to suggest.

Thanks for the review Salgado.

>On Fri, 2009-09-04 at 15:35 +0000, Barry Warsaw wrote:
>> === modified file 'lib/lp/registry/browser/person.py'
>> --- lib/lp/registry/browser/person.py 2009-09-04 02:20:59 +0000
>> +++ lib/lp/registry/browser/person.py 2009-09-04 12:56:38 +0000
>> @@ -39,6 +39,7 @@
>> 'PersonFacets',
>> 'PersonGPGView',
>> 'PersonIndexView',
>> + 'PersonKarmaView',
>> 'PersonLanguagesView',
>> 'PersonLatestQuestionsView',
>> 'PersonNavigation',
>> @@ -2507,8 +2508,26 @@
>> rootsite='answers')
>>
>>
>> +class PersonKarmaView(LaunchpadView):
>> + """A view class used for ~person/+karma."""
>> +
>> + @property
>> + def label(self):
>> + return 'Launchpad Karma for ' + cgi.escape(self.user.displayname)
>> +
>> + @cachedproperty
>> + def has_karma(self):
>> + """Does the have karma?"""
>
>s/the have/the person have/?

Fixed.

>
>> + return bool(self.context.karma_category_caches)
>> +
>> + @cachedproperty
>> + def has_expired_karma(self):
>> + """Did the user have karma?"""
>
>s/user/person. User is the term we use to refer to the person who's
>interacting with LP, and here we're talking about the context of the
>view.
>
>I know you just copied-and-pasted them, but these are small enough
>changes that I'm sure you won't mind doing. ;)

I don't, and thanks! Fixed.

>> === modified file 'lib/lp/registry/templates/person-karma.pt'
>> --- lib/lp/registry/templates/person-karma.pt 2009-07-17 17:59:07 +0000
>> +++ lib/lp/registry/templates/person-karma.pt 2009-09-03 00:38:35 +0000
>[...]
>> -</body>
>> + >
>> + <body>
>> + <div metal:fill-slot="main"
>> + tal:define="latest_karma context/latestKarma">
>> +
>> + <p class="application-summary">
>> + This is a summary of the Launchpad karma earned by
>> + <span tal:replace="context/displayname" />, organized by activity
>> + type.
>> + </p>
>> +
>> + <p id="no-karma" tal:condition="not: view/has_karma">
>> + <tal:expired condition="view/has_expired_karma">
>> + <tal:name replace="context/displayname" />'s karma has expired.
>> + </tal:expired>
>> + <tal:never condition="not: view/has_expired_karma">
>> + No karma has yet been assigned to
>> + <tal:name replace="context/displayname" />.
>> + Karma is updated daily.
>> + </tal:never>
>> + </p>
>> +
>> + <table class="cozy-listing" id="karmapoints"
>> + tal:condition="context/karma_category_caches">
>> + <tbody>
>> + <tr tal:repeat="cache context/karma_category_caches">
>> + <td tal:content="cache/category/title">Bug Management</td>
>> + <td><strong
>> + class=""
>> + tal:attributes="class string: ${cache/category/name}-stat"
>> + tal:content="cache/karmavalue">10</strong></td>
>> + </tr>
>> + </tbody>
>> + </table>
>> + <b>Total karma:</b>
>> ...

Read more...

Revision history for this message
Guilherme Salgado (salgado) :
review: Approve
Revision history for this message
Barry Warsaw (barry) wrote :
Download full text (12.8 KiB)

On Sep 08, 2009, at 02:20 PM, Guilherme Salgado wrote:

>On Tue, 2009-09-08 at 10:42 -0400, Barry Warsaw wrote:
>> On Sep 04, 2009, at 06:42 PM, Guilherme Salgado wrote:
>>
>> >Thanks a lot for your work on defining the heading rules and
>> >implementing them. I have a few suggestions below, and I'll send a
>> >separate review for the changes on the karma page.
>>
>> Thanks for the review Salgado. I'm still working on getting all the tests to
>> pass, and OCR last week cut into my time on this branch, but I'm keen to
>> resolve the issues and get this landed so we have time to update our pages to
>> the new rules.
>>
>> >Also, the 404 page now has a title which doesn't reflect that.
>> ><https://code.launchpad.dev/~mark/firefox/release-0.8/+merge/1111>
>>
>> Yep, there are no merge proposals in sample data, so you have to create this
>> manually.
>
>Right, but what I mean is that the title of our 404 pages now have the
>breadcrumbs in reversed order instead of the 'Not found here' we were
>used to.

This turned out to be surprisingly tricky to solve! We discussed various
options on IRC and came up with using tales formatter, e.g. view/fmt:pagetitle
which simplified a lot of things (the attached incremental diff
notwithstanding).

The nice thing is that this should deprecate CONTEXTS/fmt:pagetitle and
PageTemplateContextsAPI in several places, and I've filed bug 426532 to
eventually remove that stuff.

>Your changes look good to me. There's just one extra blank line that
>ought to be removed.

Actually, I was able to get rid of the whole view class, so I cleaned that up
while I was at it. Here's the incremental.

=== modified file 'lib/canonical/launchpad/browser/launchpad.py'
--- lib/canonical/launchpad/browser/launchpad.py 2009-09-08 14:42:20 +0000
+++ lib/canonical/launchpad/browser/launchpad.py 2009-09-08 21:12:07 +0000
@@ -19,7 +19,6 @@
     'MaintenanceMessage',
     'MenuBox',
     'NavigationMenuTabs',
- 'PageTitleView',
     'SoftTimeoutView',
     'StructuralHeaderPresentation',
     'StructuralObjectPresentation',
@@ -27,9 +26,6 @@
     ]

-COLON = ' : '
-
-
 import cgi
 import operator
 import os
@@ -40,7 +36,7 @@

 from zope.app import zapi
 from zope.datetime import parseDatetimetz, tzinfo, DateTimeError
-from zope.component import getMultiAdapter, getUtility, queryAdapter
+from zope.component import getUtility, queryAdapter
 from zope.interface import implements
 from zope.publisher.interfaces import NotFound
 from zope.publisher.interfaces.browser import IBrowserPublisher
@@ -289,28 +285,6 @@
         return len(self.items) > 1

-class PageTitleView(LaunchpadView):
- """View for page <title>."""
-
- def __init__(self, context, request):
- super(PageTitleView, self).__init__(context, request)
- self.hierarchy_view = getMultiAdapter(
- (self.context, self.request), name='+hierarchy')
-
- @cachedproperty
- def page_title(self):
- """The page title, constructed from the reversed breadcrumbs."""
- return COLON.join(
- breadcrumb.text for breadcrumb
- in reversed(self.hierarchy_view.items))
-
- @property
- def has_custom_title(self):
- ...

Revision history for this message
Guilherme Salgado (salgado) wrote :
Download full text (7.4 KiB)

Hi Barry,

This is a nice change. I see only two small things that need to be
changed.

Cheers,

On Tue, 2009-09-08 at 18:42 -0400, Barry Warsaw wrote:
> On Sep 08, 2009, at 02:20 PM, Guilherme Salgado wrote:
>
> >On Tue, 2009-09-08 at 10:42 -0400, Barry Warsaw wrote:
> >> On Sep 04, 2009, at 06:42 PM, Guilherme Salgado wrote:
> >>
> >> >Thanks a lot for your work on defining the heading rules and
> >> >implementing them. I have a few suggestions below, and I'll send a
> >> >separate review for the changes on the karma page.
> >>
> >> Thanks for the review Salgado. I'm still working on getting all the tests to
> >> pass, and OCR last week cut into my time on this branch, but I'm keen to
> >> resolve the issues and get this landed so we have time to update our pages to
> >> the new rules.
> >>
> >> >Also, the 404 page now has a title which doesn't reflect that.
> >> ><https://code.launchpad.dev/~mark/firefox/release-0.8/+merge/1111>
> >>
> >> Yep, there are no merge proposals in sample data, so you have to create this
> >> manually.
> >
> >Right, but what I mean is that the title of our 404 pages now have the
> >breadcrumbs in reversed order instead of the 'Not found here' we were
> >used to.
>
> This turned out to be surprisingly tricky to solve! We discussed various
> options on IRC and came up with using tales formatter, e.g. view/fmt:pagetitle
> which simplified a lot of things (the attached incremental diff
> notwithstanding).
>
> The nice thing is that this should deprecate CONTEXTS/fmt:pagetitle and
> PageTemplateContextsAPI in several places, and I've filed bug 426532 to
> eventually remove that stuff.
>
> >Your changes look good to me. There's just one extra blank line that
> >ought to be removed.
>
> Actually, I was able to get rid of the whole view class, so I cleaned that up
> while I was at it. Here's the incremental.
>

> === modified file 'lib/canonical/launchpad/webapp/tales.py'
> --- lib/canonical/launchpad/webapp/tales.py 2009-09-04 13:00:42 +0000
> +++ lib/canonical/launchpad/webapp/tales.py 2009-09-08 22:31:49 +0000
> @@ -16,6 +16,8 @@
> import rfc822
> import sys
> import urllib
> +import warnings
> +
> from xml.sax.saxutils import unescape as xml_unescape
> from datetime import datetime, timedelta
> from lazr.enum import enumerated_type_registry
> @@ -62,6 +64,9 @@
> from lp.soyuz.interfaces.build import BuildStatus
>
>
> +SEPARATOR = ' : '
> +
> +
> def escape(text, quote=True):
> """Escape text for insertion into HTML.
>
> @@ -439,7 +444,12 @@
> # constants, so it's not a problem. We might want to use something like
> # frozenset (http://code.activestate.com/recipes/414283/) here, though.
> # The names which can be traversed further (e.g context/fmt:url/+edit).
> - traversable_names = {'link': 'link', 'url': 'url', 'api_url': 'api_url'}
> + traversable_names = {
> + 'api_url': 'api_url',
> + 'link': 'link',
> + 'pagetitle': 'pagetitle',
> + 'url': 'url',
> + }
>
> # Names which are allowed but can't be traversed further.
> final_traversable_names = {

Isn't pagetitle a final_traversable_name?

> @@ -534,6 +544,67 @@
> ...

Read more...

Revision history for this message
Barry Warsaw (barry) wrote :

On Sep 9, 2009, at 9:27 AM, Guilherme Salgado wrote:

> This is a nice change. I see only two small things that need to be
> changed.

Hi Salgado. With all the stress in trying to get this branch landed,
I forgot about these comments. I'll get this in a follow up branch
this week.

Thanks!

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-09-02 12:39:24 +0000
3+++ lib/canonical/launchpad/browser/launchpad.py 2009-09-03 20:26:49 +0000
4@@ -26,6 +26,9 @@
5 ]
6
7
8+COLON = ' : '
9+
10+
11 import cgi
12 import operator
13 import os
14@@ -256,6 +259,12 @@
15 breadcrumbs.append(page_crumb)
16 return breadcrumbs
17
18+ @cachedproperty
19+ def page_title(self):
20+ """The page title, constructed from the reversed breadcrumbs."""
21+ return COLON.join(
22+ breadcrumb.text for breadcrumb in reversed(self.items))
23+
24 def makeBreadcrumbForRequestedPage(self):
25 """Return an `IBreadcrumb` for the requested page.
26
27
28=== modified file 'lib/canonical/launchpad/icing/style-3-0.css'
29--- lib/canonical/launchpad/icing/style-3-0.css 2009-09-04 11:25:26 +0000
30+++ lib/canonical/launchpad/icing/style-3-0.css 2009-09-04 12:56:38 +0000
31@@ -681,6 +681,20 @@
32 table.listing th {
33 font-weight: bold;
34 }
35+table.narrow-listing {
36+ width: 45em;
37+}
38+/* ~person/+karma */
39+table.cozy-listing {
40+ width: 20em;
41+ background-color: #fff;
42+ border: 1px solid #d2d2d2;
43+ border-bottom: 1px solid #d2d2d2;
44+}
45+table.cozy-listing td {
46+ border: 1px #d2d2d2;
47+ border-style: dotted none none none;
48+}
49
50 ul.language, li.language {
51 list-style-image: url(/@@/language);
52@@ -743,7 +757,3 @@
53 font-weight: normal;
54 padding: 2px 1em 2px 2px;
55 }
56-
57-table.narrow-listing {
58- width: 45em;
59-}
60
61=== modified file 'lib/canonical/launchpad/templates/launchpad-form.pt'
62--- lib/canonical/launchpad/templates/launchpad-form.pt 2009-08-01 02:04:55 +0000
63+++ lib/canonical/launchpad/templates/launchpad-form.pt 2009-09-03 15:39:22 +0000
64@@ -14,11 +14,6 @@
65 enctype="multipart/form-data"
66 accept-charset="UTF-8">
67
68- <h1 tal:condition="view/label"
69- tal:content="view/label"
70- metal:define-slot="heading"
71- >Add Something</h1>
72-
73 <div metal:define-macro="formbody">
74
75 <p metal:define-slot="extra_info" tal:replace="nothing">
76@@ -38,8 +33,8 @@
77 Schema validation errors.
78 </p>
79
80- <div class="row"
81- metal:define-slot="extra_top"
82+ <div class="row"
83+ metal:define-slot="extra_top"
84 tal:replace="nothing">
85 <div>Extra top</div>
86 <div><input type="text"/></div>
87@@ -55,7 +50,7 @@
88 tal:content="structure script" />
89
90 <div class="row"
91- metal:define-slot="extra_bottom"
92+ metal:define-slot="extra_bottom"
93 tal:replace="nothing">
94 <div>Extra bottom</div>
95 <div class="field"><input type="text" /></div>
96@@ -73,8 +68,8 @@
97 </tal:has-cancel-link>
98 </div>
99
100- <div class="row"
101- metal:define-slot="extra_buttons"
102+ <div class="row"
103+ metal:define-slot="extra_buttons"
104 tal:replace="nothing">
105 </div>
106
107
108=== modified file 'lib/canonical/launchpad/versioninfo.py'
109--- lib/canonical/launchpad/versioninfo.py 2009-06-25 05:30:52 +0000
110+++ lib/canonical/launchpad/versioninfo.py 2009-09-02 19:11:01 +0000
111@@ -1,7 +1,7 @@
112 # Copyright 2009 Canonical Ltd. This software is licensed under the
113 # GNU Affero General Public License version 3 (see the file LICENSE).
114
115-"""Give access to bzr version info, if available.
116+"""Give access to bzr and other version info, if available.
117
118 The bzr version info file is expected to be in the Launchpad root in the
119 file bzr-version-info.py.
120@@ -16,19 +16,28 @@
121 If the bzr-version-info.py file does not exist, then revno, date and
122 branch_nick will all be None.
123
124-If that file exists, and contains valid python, revno, date and branch_nick
125+If that file exists, and contains valid Python, revno, date and branch_nick
126 will have appropriate values from version_info.
127
128-If that file exists, and contains invalid python, there will be an error when
129+If that file exists, and contains invalid Python, there will be an error when
130 this module is loaded. This module is imported into
131 canonical/launchpad/__init__.py so that such errors are caught at start-up.
132
133+This module also reads version.txt at the top of the tree (i.e. a sibling of
134+bzr-version-info.py), which contains the Launchpad release number. If that
135+file does not exist, we make something up.
136 """
137
138+__all__ = [
139+ 'branch_nick',
140+ 'date',
141+ 'revno',
142+ 'versioninfo',
143+ ]
144+
145+
146 import imp
147
148-__all__ = ['versioninfo', 'revno', 'date', 'branch_nick']
149-
150
151 def read_version_info():
152 try:
153@@ -52,3 +61,14 @@
154 date = versioninfo.get('date')
155 branch_nick = versioninfo.get('branch_nick')
156
157+
158+try:
159+ version_file = open('version.txt')
160+except IOError:
161+ release = 'x.y.z'
162+else:
163+ try:
164+ version_data = version_file.read()
165+ release = version_data.strip()
166+ finally:
167+ version_file.close()
168
169=== modified file 'lib/canonical/launchpad/webapp/interfaces.py'
170--- lib/canonical/launchpad/webapp/interfaces.py 2009-08-24 17:47:34 +0000
171+++ lib/canonical/launchpad/webapp/interfaces.py 2009-09-03 23:06:50 +0000
172@@ -350,7 +350,7 @@
173
174 def getRootURL(rootsite):
175 """Return this request's root URL.
176-
177+
178 If rootsite is not None, then return the root URL for that rootsite,
179 looked up from our config.
180 """
181@@ -626,11 +626,11 @@
182 instance of a Zope internationalized message will cause the
183 message to be translated, then CGI escaped.
184
185- :param msg: This may be a string, an instance of
186- `zope.i18n.Message`, , or an instance of `IStructuredString`.
187+ :param msg: This may be a string, an instance of `zope.i18n.Message`,
188+ or an instance of `IStructuredString`.
189
190 :param level: One of the `BrowserNotificationLevel` values: DEBUG,
191- INFO, NOTICE, WARNING, ERROR.
192+ INFO, NOTICE, WARNING, ERROR.
193 """
194
195 def removeAllNotifications():
196@@ -796,7 +796,7 @@
197 """Get a Storm store with a desired flavor.
198
199 Stores come in two flavors - MASTER_FLAVOR and SLAVE_FLAVOR.
200-
201+
202 The master is writable and up to date, but we should not use it
203 whenever possible because there is only one master and we don't want
204 it to be overloaded.
205
206=== modified file 'lib/lp/app/browser/configure.zcml'
207--- lib/lp/app/browser/configure.zcml 2009-08-27 09:06:42 +0000
208+++ lib/lp/app/browser/configure.zcml 2009-09-03 19:12:20 +0000
209@@ -38,6 +38,15 @@
210 permission="zope.Public"
211 />
212
213+ <!-- Page title reversed breadcrumbs -->
214+ <browser:page
215+ for="zope.interface.Interface"
216+ name="+page-title"
217+ class="canonical.launchpad.browser.launchpad.Hierarchy"
218+ template="../templates/launchpad-page-title.pt"
219+ permission="zope.Public"
220+ />
221+
222 <!-- TALES watermark: namespace -->
223 <adapter
224 for="*"
225
226=== modified file 'lib/lp/app/browser/tests/watermark.txt'
227--- lib/lp/app/browser/tests/watermark.txt 2009-08-19 23:11:16 +0000
228+++ lib/lp/app/browser/tests/watermark.txt 2009-09-03 23:06:50 +0000
229@@ -1,3 +1,4 @@
230+===============================
231 The "watermark" TALES formatter
232 ===============================
233
234@@ -7,83 +8,131 @@
235
236
237 Watermark headings
238-------------------
239+==================
240
241 The `watermark:heading` is used when you want a heading for the nearest object
242 that implements IRootContext.
243
244-Objects that directly provide IRootContext get an h1 heading, those that don't
245-get an h2 heading (although why I'm not yet certain).
246-
247 >>> from lp.testing import test_tales
248+ >>> class view:
249+ ... def __init__(self, context):
250+ ... self.context = context
251
252 Products directly implement IRootContext.
253
254 >>> widget = factory.makeProduct(title='Widget')
255- >>> print test_tales('project/watermark:heading', project=widget)
256- <h1>Widget</h1>
257+ >>> print test_tales('view/watermark:heading', view=view(widget))
258+ <h...>Widget</h...>
259
260 A series of the product still show the product watermark.
261
262 >>> dev_focus = widget.development_focus
263- >>> print test_tales('series/watermark:heading', series=dev_focus)
264- <h2>Widget</h2>
265+ >>> print test_tales('view/watermark:heading', view=view(dev_focus))
266+ <h...>Widget</h...>
267
268 Projects also directly implement IRootContext ...
269
270 >>> kde = factory.makeProject(title='KDE')
271- >>> print test_tales('project/watermark:heading', project=kde)
272- <h1>KDE</h1>
273+ >>> print test_tales('view/watermark:heading', view=view(kde))
274+ <h...>KDE</h...>
275
276 ... as do distributions ...
277
278 >>> mint = factory.makeDistribution(title='Mint Linux')
279- >>> print test_tales(
280- ... "distro/watermark:heading", distro=mint)
281- <h1>Mint Linux</h1>
282+ >>> print test_tales('view/watermark:heading', view=view(mint))
283+ <h...>Mint Linux</h...>
284
285 ... and people ...
286
287 >>> eric = factory.makePerson(displayname="Eric the Viking")
288- >>> print test_tales("person/watermark:heading", person=eric)
289- <h1>Eric the Viking</h1>
290+ >>> print test_tales('view/watermark:heading', view=view(eric))
291+ <h...>Eric the Viking</h...>
292
293 ... and sprints.
294
295 >>> sprint = factory.makeSprint(title="Launchpad Epic")
296- >>> print test_tales("sprint/watermark:heading", sprint=sprint)
297- <h1>Launchpad Epic</h1>
298+ >>> print test_tales('view/watermark:heading', view=view(sprint))
299+ <h...>Launchpad Epic</h...>
300
301 If there is no root context defined for the object, then the heading is
302 'Launchpad.net' (differentiating from the Launchpad project within
303 Launchpad.net).
304
305 >>> machine = factory.makeCodeImportMachine()
306- >>> print test_tales(
307- ... "machine/watermark:heading", machine=machine)
308- <h2>Launchpad.net</h2>
309+ >>> print test_tales('view/watermark:heading', view=view(machine))
310+ <h...>Launchpad.net</h...>
311
312-Any html in the context title will be escaped to avoid XSS vulnerabilities.
313+Any HTML in the context title will be escaped to avoid XSS vulnerabilities.
314
315 >>> person = factory.makePerson(
316 ... displayname="Fubar<br/><script>alert('XSS')</script>")
317- >>> print test_tales("person/watermark:heading", person=person)
318- <h1>Fubar&lt;br/&gt;&lt;script&gt;alert('XSS')&lt;/script&gt;</h1>
319+ >>> print test_tales('view/watermark:heading', view=view(person))
320+ <h...>Fubar&lt;br/&gt;&lt;script&gt;alert('XSS')&lt;/script&gt;</h...>
321
322
323 Watermark images
324-------------------
325+================
326
327 The image for the watermark is determined effectively by context/image:logo.
328
329- >>> print test_tales("series/watermark:logo", series=dev_focus)
330+ >>> print test_tales('view/watermark:logo', view=view(dev_focus))
331 <img ... src="/@@/product-logo" />
332
333- >>> print test_tales("person/watermark:logo", person=eric)
334+ >>> print test_tales('view/watermark:logo', view=view(eric))
335 <img ... src="/@@/person-logo" />
336
337-If there is no root context, the launchpad logo is shown.
338+If there is no root context, the Launchpad logo is shown.
339
340- >>> print test_tales(
341- ... "machine/watermark:logo", machine=machine)
342+ >>> print test_tales('view/watermark:logo', view=view(machine))
343 <img ... src="/@@/launchpad-logo" />
344+
345+
346+Heading level
347+=============
348+
349+The watermark heading is shown above the application tabs, in either H1 level
350+or H2 level. The heading level is determined by the view. For the index view
351+of the context, H1 is used. For all non-index pages, i.e. subpages, H2 is
352+used.
353+
354+The choice of heading level is controlled by a marker interface on the view.
355+Normally, the view class does not implement the marker interface, meaning it
356+is not the index page of the context. In this case the heading is rendered in
357+H2.
358+
359+ >>> print test_tales('view/watermark:heading', view=view(widget))
360+ <h2>Widget</h2>
361+
362+If the view class implements IIndexView, then this is the index page for the
363+context and the heading is rendered in H1.
364+
365+ >>> from zope.interface import implements
366+ >>> from lp.app.interfaces.headings import IIndexView
367+
368+ >>> class view:
369+ ... implements(IIndexView)
370+ ... def __init__(self, context):
371+ ... self.context = context
372+
373+ >>> print test_tales('view/watermark:heading', view=view(widget))
374+ <h1>Widget</h1>
375+
376+
377+Editable headings
378+=================
379+
380+Some index pages also allow for editing the context's description. To specify
381+this, the view must implement IEditableContextTitle and provide an method
382+that returns all the HTML necessary to edit the context, including the
383+appropriate H tag.
384+
385+ >>> from lp.app.interfaces.headings import IEditableContextTitle
386+ >>> class view:
387+ ... implements(IEditableContextTitle)
388+ ... def __init__(self, context):
389+ ... self.context = context
390+ ... def title_edit_widget(self):
391+ ... return '<h0>EDIT ME</h0>'
392+
393+ >>> print test_tales('view/watermark:heading', view=view(widget))
394+ <h0>EDIT ME</h0>
395
396=== modified file 'lib/lp/app/browser/watermark.py'
397--- lib/lp/app/browser/watermark.py 2009-08-19 22:44:27 +0000
398+++ lib/lp/app/browser/watermark.py 2009-09-03 23:06:50 +0000
399@@ -18,7 +18,8 @@
400
401 from canonical.lazr.canonicalurl import nearest_provides_or_adapted
402
403-from lp.app.interfaces.rootcontext import IRootContext
404+from lp.app.interfaces.headings import (
405+ IEditableContextTitle, IIndexView, IRootContext)
406
407
408 class WatermarkTalesAdapter:
409@@ -26,34 +27,43 @@
410
411 implements(ITraversable)
412
413- def __init__(self, context):
414- self._context = context
415+ def __init__(self, view):
416+ self._view = view
417+ self._context = view.context
418
419 @property
420 def root_context(self):
421 return nearest_provides_or_adapted(self._context, IRootContext)
422
423 def heading(self):
424- """Return the heading text for the root context.
425-
426- If the context itself provides IRootContext then we return an H1,
427- otherwise it is a H2.
428+ """Return the heading text for the page.
429+
430+ If the view provides `IEditableContextTitle` then the top heading is
431+ rendered from the view's `title_edit_widget` and is generally
432+ editable.
433+
434+ Otherwise, if the context provides `IRootContext` then we return an
435+ H1, else an H2.
436 """
437- if IRootContext.providedBy(self._context):
438+ # Check the view; is the title editable?
439+ if IEditableContextTitle.providedBy(self._view):
440+ return self._view.title_edit_widget()
441+ # The title is static, but only the root context gets an H1.
442+ if IIndexView.providedBy(self._view):
443 heading = 'h1'
444 else:
445 heading = 'h2'
446-
447- root = self.root_context
448- if root is None:
449+ # If there is actually no root context, then it's a top-level
450+ # context-less page so Launchpad.net is shown as the branding.
451+ if self.root_context is None:
452 title = 'Launchpad.net'
453 else:
454- title = root.title
455-
456- return "<%(heading)s>%(title)s</%(heading)s>" % {
457- 'heading': heading,
458- 'title': cgi.escape(title)
459- }
460+ title = self.root_context.title
461+ # For non-editable titles, generate the static heading.
462+ return "<%(heading)s>%(title)s</%(heading)s>" % dict(
463+ heading=heading,
464+ title=cgi.escape(title),
465+ )
466
467 def logo(self):
468 """Return the logo image for the root context."""
469@@ -66,4 +76,4 @@
470 elif name == "logo":
471 return self.logo()
472 else:
473- raise TraversalError, name
474+ raise TraversalError(name)
475
476=== modified file 'lib/lp/app/interfaces/__init__.py'
477--- lib/lp/app/interfaces/__init__.py 2009-08-19 05:33:26 +0000
478+++ lib/lp/app/interfaces/__init__.py 2009-09-03 23:06:50 +0000
479@@ -1,1 +0,0 @@
480-"""Interfaces for the Launchpad application."""
481
482=== renamed file 'lib/lp/app/interfaces/rootcontext.py' => 'lib/lp/app/interfaces/headings.py'
483--- lib/lp/app/interfaces/rootcontext.py 2009-08-19 06:18:41 +0000
484+++ lib/lp/app/interfaces/headings.py 2009-09-04 12:59:09 +0000
485@@ -1,10 +1,12 @@
486 # Copyright 2009 Canonical Ltd. This software is licensed under the
487 # GNU Affero General Public License version 3 (see the file LICENSE).
488
489-"""The primary context interface."""
490+"""Interfaces for headings and breadcrumbs."""
491
492 __metaclass__ = type
493 __all__ = [
494+ 'IEditableContextTitle',
495+ 'IIndexView',
496 'IRootContext',
497 ]
498
499@@ -16,3 +18,16 @@
500 """Something that is an object off the Launchpad root."""
501
502 title = Attribute('The title of the root context object.')
503+
504+
505+class IIndexView(Interface):
506+ """The index view of the current context."""
507+
508+ title = Attribute('The title of the context object.')
509+
510+
511+class IEditableContextTitle(Interface):
512+ """Interface specifying that the context has an editable title."""
513+
514+ def title_edit_widget():
515+ """Return the HTML of the editable title widget."""
516
517=== modified file 'lib/lp/app/templates/base-layout.pt'
518--- lib/lp/app/templates/base-layout.pt 2009-08-31 12:01:46 +0000
519+++ lib/lp/app/templates/base-layout.pt 2009-09-03 19:12:20 +0000
520@@ -4,7 +4,8 @@
521 define-macro="master"
522 tal:define="
523 revno modules/canonical.launchpad.versioninfo/revno | string:unknown;
524- version string:Launchpad 2.2.7 (r${revno});
525+ release modules/canonical.launchpad.versioninfo/release;
526+ version string:Launchpad ${release} (r${revno});
527 devmode modules/canonical.config/config/devmode;
528 rooturl modules/canonical.launchpad.webapp.vhosts/allvhosts/configs/mainsite/rooturl;
529 is_demo modules/canonical.config/config/launchpad/is_demo;
530@@ -26,9 +27,12 @@
531 yui string:${icingroot}/yui/3.0.0pr2/build;
532 lazr_js string:${icingroot}/lazr/build;
533 lp_js string:${icingroot}/build">
534- <title i18n:translate=""
535- tal:content="view/page_title|CONTEXTS/fmt:pagetitle">Launchpad</title>
536-
537+ <title tal:condition="context/@@+page-title/display_breadcrumbs"
538+ tal:replace="structure context/@@+page-title">The Title</title>
539+ <title tal:condition="not: context/@@+page-title/display_breadcrumbs"
540+ i18n:translate=""
541+ tal:content="view/page_title|CONTEXTS/fmt:pagetitle"
542+ >Launchpad</title>
543 <link rel="shortcut icon" href="/@@/launchpad.png" />
544
545 <tal:atomfeeds define="feed_links view/feed_links | nothing">
546@@ -92,8 +96,8 @@
547
548 <div class="watermark-apps-portlet top-portlet"
549 tal:condition="view/macro:pagehas/applicationtabs">
550- <span tal:replace="structure context/watermark:logo"></span>
551- <h2 tal:replace="structure context/watermark:heading">
552+ <span tal:replace="structure view/watermark:logo"></span>
553+ <h2 tal:replace="structure view/watermark:heading">
554 Celso Providelo
555 </h2>
556 <metal:heading_nav
557@@ -119,10 +123,10 @@
558 xml:lang view/lang|default_language|default;
559 dir view/dir|string:ltr">
560 <div tal:condition="view/macro:pagehas/applicationtabs">
561- <h2
562- tal:condition="context/title|nothing"
563- tal:content="context/title"
564- metal:define-slot="heading" />
565+ <h1 tal:condition="view/label"
566+ tal:content="view/label"
567+ metal:define-slot="heading"
568+ >Page Label</h1>
569 <tal:breadcrumbs replace="structure context/@@+hierarchy">
570 ProjectName > Branches > Merge Proposals > fix-for-navigation
571 </tal:breadcrumbs>
572
573=== added file 'lib/lp/app/templates/launchpad-page-title.pt'
574--- lib/lp/app/templates/launchpad-page-title.pt 1970-01-01 00:00:00 +0000
575+++ lib/lp/app/templates/launchpad-page-title.pt 2009-09-03 19:12:20 +0000
576@@ -0,0 +1,7 @@
577+<title
578+ xmlns="http://www.w3.org/1999/xhtml"
579+ xmlns:tal="http://xml.zope.org/namespaces/tal"
580+ xmlns:metal="http://xml.zope.org/namespaces/metal"
581+ xmlns:i18n="http://xml.zope.org/namespaces/i18n"
582+ i18n:domain="launchpad"
583+ tal:content="view/page_title">Page Title</title>
584
585=== modified file 'lib/lp/blueprints/interfaces/sprint.py'
586--- lib/lp/blueprints/interfaces/sprint.py 2009-08-19 23:11:16 +0000
587+++ lib/lp/blueprints/interfaces/sprint.py 2009-09-03 23:06:50 +0000
588@@ -27,7 +27,7 @@
589 PublicPersonChoice)
590 from canonical.launchpad.validators.name import name_validator
591 from canonical.launchpad.interfaces.launchpad import IHasDrivers
592-from lp.app.interfaces.rootcontext import IRootContext
593+from lp.app.interfaces.headings import IRootContext
594 from lp.registry.interfaces.role import IHasOwner
595 from lp.blueprints.interfaces.specificationtarget import IHasSpecifications
596
597
598=== modified file 'lib/lp/bugs/browser/configure.zcml'
599--- lib/lp/bugs/browser/configure.zcml 2009-09-04 11:08:20 +0000
600+++ lib/lp/bugs/browser/configure.zcml 2009-09-04 12:56:38 +0000
601@@ -788,6 +788,12 @@
602 class="lp.code.browser.branch.BranchHierarchy"
603 template="../../app/templates/launchpad-hierarchy.pt"
604 permission="zope.Public"/>
605+ <browser:page
606+ for="lp.bugs.interfaces.bugbranch.IBugBranch"
607+ name="+page-title"
608+ class="lp.code.browser.branch.BranchHierarchy"
609+ template="../../app/templates/launchpad-page-title.pt"
610+ permission="zope.Public"/>
611 <browser:url
612 for="lp.bugs.interfaces.bugbranch.IBugBranch"
613 path_expression="string:+bug/${bug/id}"
614
615=== modified file 'lib/lp/code/browser/configure.zcml'
616--- lib/lp/code/browser/configure.zcml 2009-09-02 23:14:55 +0000
617+++ lib/lp/code/browser/configure.zcml 2009-09-03 23:06:50 +0000
618@@ -10,7 +10,7 @@
619 i18n_domain="launchpad">
620
621 <adapter
622- provides="lp.app.interfaces.rootcontext.IRootContext"
623+ provides="lp.app.interfaces.headings.IRootContext"
624 for="lp.code.interfaces.branch.IBranch"
625 factory="lp.code.browser.branch.branch_root_context"/>
626
627@@ -159,6 +159,12 @@
628 class="lp.code.browser.branch.BranchHierarchy"
629 template="../../app/templates/launchpad-hierarchy.pt"
630 permission="zope.Public"/>
631+ <browser:page
632+ for="lp.code.interfaces.branchmergeproposal.IBranchMergeProposal"
633+ name="+page-title"
634+ class="lp.code.browser.branch.BranchHierarchy"
635+ template="../../app/templates/launchpad-page-title.pt"
636+ permission="zope.Public"/>
637 <browser:navigation
638 module="lp.code.browser.branchmergeproposal"
639 classes="
640@@ -323,6 +329,12 @@
641 class="lp.code.browser.branch.BranchHierarchy"
642 template="../../app/templates/launchpad-hierarchy.pt"
643 permission="zope.Public"/>
644+ <browser:page
645+ for="lp.code.interfaces.branchsubscription.IBranchSubscription"
646+ name="+page-title"
647+ class="lp.code.browser.branch.BranchHierarchy"
648+ template="../../app/templates/launchpad-page-title.pt"
649+ permission="zope.Public"/>
650 <browser:defaultView
651 for="lp.code.interfaces.branchsubscription.IBranchSubscription"
652 name="+index"/>
653@@ -344,6 +356,12 @@
654 class="lp.code.browser.branch.BranchHierarchy"
655 template="../../app/templates/launchpad-hierarchy.pt"
656 permission="zope.Public"/>
657+ <browser:page
658+ for="lp.code.interfaces.branch.IBranch"
659+ name="+page-title"
660+ class="lp.code.browser.branch.BranchHierarchy"
661+ template="../../app/templates/launchpad-page-title.pt"
662+ permission="zope.Public"/>
663 <browser:defaultView
664 for="lp.code.interfaces.branch.IBranch"
665 name="+index"/>
666@@ -561,6 +579,12 @@
667 class="lp.code.browser.branch.BranchHierarchy"
668 template="../../app/templates/launchpad-hierarchy.pt"
669 permission="zope.Public"/>
670+ <browser:page
671+ for="lp.code.interfaces.codereviewcomment.ICodeReviewComment"
672+ name="+page-title"
673+ class="lp.code.browser.branch.BranchHierarchy"
674+ template="../../app/templates/launchpad-page-title.pt"
675+ permission="zope.Public"/>
676 <browser:url
677 for="lp.code.interfaces.codereviewcomment.ICodeReviewComment"
678 path_expression="string:comments/${id}"
679
680=== modified file 'lib/lp/code/browser/tests/test_branch.py'
681--- lib/lp/code/browser/tests/test_branch.py 2009-08-19 06:18:41 +0000
682+++ lib/lp/code/browser/tests/test_branch.py 2009-09-03 23:06:50 +0000
683@@ -20,7 +20,7 @@
684 from canonical.config import config
685 from canonical.database.constants import UTC_NOW
686
687-from lp.app.interfaces.rootcontext import IRootContext
688+from lp.app.interfaces.headings import IRootContext
689 from lp.code.browser.branch import (
690 BranchAddView, BranchMirrorStatusView, BranchReviewerEditView,
691 BranchSparkView, BranchView)
692
693=== modified file 'lib/lp/registry/browser/configure.zcml'
694--- lib/lp/registry/browser/configure.zcml 2009-09-02 13:28:19 +0000
695+++ lib/lp/registry/browser/configure.zcml 2009-09-03 00:38:35 +0000
696@@ -846,14 +846,19 @@
697 <browser:pages
698 for="lp.registry.interfaces.person.IPerson"
699 permission="zope.Public"
700+ class="lp.registry.browser.person.PersonKarmaView">
701+ <browser:page
702+ name="+karma"
703+ template="../templates/person-karma.pt"/>
704+ </browser:pages>
705+ <browser:pages
706+ for="lp.registry.interfaces.person.IPerson"
707+ permission="zope.Public"
708 class="lp.registry.browser.person.PersonView">
709 <browser:page
710 name="+participation"
711 template="../templates/person-participation.pt"/>
712 <browser:page
713- name="+karma"
714- template="../templates/person-karma.pt"/>
715- <browser:page
716 name="+sshkeys"
717 attribute="showSSHKeys"/>
718 <browser:page
719
720=== modified file 'lib/lp/registry/browser/person.py'
721--- lib/lp/registry/browser/person.py 2009-09-04 02:20:59 +0000
722+++ lib/lp/registry/browser/person.py 2009-09-04 12:56:38 +0000
723@@ -39,6 +39,7 @@
724 'PersonFacets',
725 'PersonGPGView',
726 'PersonIndexView',
727+ 'PersonKarmaView',
728 'PersonLanguagesView',
729 'PersonLatestQuestionsView',
730 'PersonNavigation',
731@@ -2507,8 +2508,26 @@
732 rootsite='answers')
733
734
735+class PersonKarmaView(LaunchpadView):
736+ """A view class used for ~person/+karma."""
737+
738+ @property
739+ def label(self):
740+ return 'Launchpad Karma for ' + cgi.escape(self.user.displayname)
741+
742+ @cachedproperty
743+ def has_karma(self):
744+ """Does the have karma?"""
745+ return bool(self.context.karma_category_caches)
746+
747+ @cachedproperty
748+ def has_expired_karma(self):
749+ """Did the user have karma?"""
750+ return self.context.latestKarma().count() > 0
751+
752+
753 class PersonView(LaunchpadView, FeedsMixin):
754- """A View class used in almost all Person's pages."""
755+ """A view class used in almost all Person's pages."""
756
757 @cachedproperty
758 def recently_approved_members(self):
759@@ -2900,16 +2919,6 @@
760 else:
761 return getUtility(ILaunchpadCelebrities).english.englishname
762
763- @cachedproperty
764- def has_karma(self):
765- """Does the have karma?"""
766- return bool(self.context.karma_category_caches)
767-
768- @cachedproperty
769- def has_expired_karma(self):
770- """Did the user have karm?."""
771- return self.context.latestKarma().count() > 0
772-
773 @property
774 def public_private_css(self):
775 """The CSS classes that represent the public or private state."""
776
777=== modified file 'lib/lp/registry/browser/product.py'
778--- lib/lp/registry/browser/product.py 2009-09-04 02:20:59 +0000
779+++ lib/lp/registry/browser/product.py 2009-09-04 12:56:38 +0000
780@@ -54,6 +54,7 @@
781 from lazr.delegates import delegates
782 from canonical.launchpad import _
783 from canonical.launchpad.fields import PillarAliases, PublicPersonChoice
784+from lp.app.interfaces.headings import IEditableContextTitle
785 from lp.bugs.interfaces.bugtask import RESOLVED_BUGTASK_STATUSES
786 from lp.bugs.interfaces.bugwatch import IBugTracker
787 from lp.services.worlddata.interfaces.country import ICountry
788@@ -776,7 +777,7 @@
789 ProductDownloadFileMixin, UsesLaunchpadMixin):
790
791 __used_for__ = IProduct
792- implements(IProductActionMenu)
793+ implements(IProductActionMenu, IEditableContextTitle)
794
795 def __init__(self, context, request):
796 HasAnnouncementsView.__init__(self, context, request)
797
798=== modified file 'lib/lp/registry/interfaces/distribution.py'
799--- lib/lp/registry/interfaces/distribution.py 2009-08-28 06:38:41 +0000
800+++ lib/lp/registry/interfaces/distribution.py 2009-09-03 23:06:50 +0000
801@@ -36,7 +36,7 @@
802 Description, PublicPersonChoice, Summary, Title)
803 from canonical.launchpad.interfaces.structuralsubscription import (
804 IStructuralSubscriptionTarget)
805-from lp.app.interfaces.rootcontext import IRootContext
806+from lp.app.interfaces.headings import IRootContext
807 from lp.registry.interfaces.announcement import IMakesAnnouncements
808 from lp.bugs.interfaces.bugtarget import (
809 IBugTarget, IOfficialBugTagTargetPublic, IOfficialBugTagTargetRestricted)
810
811=== modified file 'lib/lp/registry/interfaces/person.py'
812--- lib/lp/registry/interfaces/person.py 2009-08-31 22:16:32 +0000
813+++ lib/lp/registry/interfaces/person.py 2009-09-03 23:06:50 +0000
814@@ -71,7 +71,7 @@
815 is_valid_public_person)
816 from canonical.launchpad.interfaces.account import AccountStatus, IAccount
817 from canonical.launchpad.interfaces.emailaddress import IEmailAddress
818-from lp.app.interfaces.rootcontext import IRootContext
819+from lp.app.interfaces.headings import IRootContext
820 from lp.code.interfaces.hasbranches import IHasBranches, IHasMergeProposals
821 from lp.registry.interfaces.irc import IIrcID
822 from lp.registry.interfaces.jabber import IJabberID
823
824=== modified file 'lib/lp/registry/interfaces/product.py'
825--- lib/lp/registry/interfaces/product.py 2009-08-28 06:16:06 +0000
826+++ lib/lp/registry/interfaces/product.py 2009-09-03 23:06:50 +0000
827@@ -40,7 +40,7 @@
828 PublicPersonChoice, Summary, Title, URIField)
829 from canonical.launchpad.interfaces.structuralsubscription import (
830 IStructuralSubscriptionTarget)
831-from lp.app.interfaces.rootcontext import IRootContext
832+from lp.app.interfaces.headings import IRootContext
833 from lp.code.interfaces.branchvisibilitypolicy import (
834 IHasBranchVisibilityPolicy)
835 from lp.code.interfaces.hasbranches import IHasBranches, IHasMergeProposals
836
837=== modified file 'lib/lp/registry/interfaces/project.py'
838--- lib/lp/registry/interfaces/project.py 2009-08-28 06:16:06 +0000
839+++ lib/lp/registry/interfaces/project.py 2009-09-03 23:06:50 +0000
840@@ -20,7 +20,7 @@
841 from canonical.launchpad import _
842 from canonical.launchpad.fields import (
843 PublicPersonChoice, Summary, Title, URIField)
844-from lp.app.interfaces.rootcontext import IRootContext
845+from lp.app.interfaces.headings import IRootContext
846 from lp.code.interfaces.branchvisibilitypolicy import (
847 IHasBranchVisibilityPolicy)
848 from lp.code.interfaces.hasbranches import IHasBranches, IHasMergeProposals
849
850=== modified file 'lib/lp/registry/templates/person-karma.pt'
851--- lib/lp/registry/templates/person-karma.pt 2009-07-17 17:59:07 +0000
852+++ lib/lp/registry/templates/person-karma.pt 2009-09-03 00:38:35 +0000
853@@ -3,92 +3,73 @@
854 xmlns:tal="http://xml.zope.org/namespaces/tal"
855 xmlns:metal="http://xml.zope.org/namespaces/metal"
856 xmlns:i18n="http://xml.zope.org/namespaces/i18n"
857- xml:lang="en"
858- lang="en"
859- dir="ltr"
860- metal:use-macro="view/macro:page/onecolumn"
861+ metal:use-macro="view/macro:page/main_only"
862 i18n:domain="launchpad"
863->
864-<body>
865-
866-<metal:leftportlets fill-slot="portlets_one">
867-</metal:leftportlets>
868-
869-<metal:rightportlets fill-slot="portlets_two">
870-</metal:rightportlets>
871-
872-<div metal:fill-slot="main"
873- tal:define="latestkarma context/latestKarma">
874-
875- <h1>Launchpad karma</h1>
876-
877- <p>
878- This is a summary of the Launchpad karma earned by
879- <span tal:replace="context/displayname" />, organized by activity
880- type.
881- </p>
882-
883- <p id="no-karma" tal:condition="not: view/has_karma">
884- <tal:expired condition="view/has_expired_karma">
885- <tal:name replace="context/displayname" />'s karma has expired.
886- </tal:expired>
887- <tal:never condition="not: view/has_expired_karma">
888- No karma has yet been assigned to
889- <tal:name replace="context/displayname" />.
890- Karma is updated daily.
891- </tal:never>
892- </p>
893-
894- <table class="listing sortable" id="karmapoints"
895- tal:condition="context/karma_category_caches">
896- <thead>
897- <tr>
898- <th>Category</th>
899- <th>Points</th>
900- </tr>
901- </thead>
902- <tbody>
903- <tr tal:repeat="cache context/karma_category_caches">
904- <td tal:content="cache/category/title">Bug Management</td>
905- <td tal:content="cache/karmavalue">10</td>
906- </tr>
907- </tbody>
908- </table>
909- <table>
910- <tr>
911- <td><b>Total:</b></td>
912- <td tal:content="context/karma">34</td>
913- </tr>
914- </table>
915-
916- <tal:latest_events condition="latestkarma">
917-
918- <h2>Latest actions</h2>
919-
920- <table class="listing sortable" id="karmaactions">
921- <thead>
922- <tr>
923- <th>Action</th>
924- <th>Date</th>
925- </tr>
926- </thead>
927- <tbody>
928- <tr tal:repeat="karma latestkarma">
929- <td tal:content="karma/action/title">Bug Filed</td>
930- <td>
931- <span class="sortkey"
932- tal:content="karma/datecreated/fmt:datetime">2005-10-06</span>
933- <span
934- tal:attributes="title karma/datecreated/fmt:datetime"
935- tal:content="karma/datecreated/fmt:approximatedate" />
936- </td>
937- </tr>
938- </tbody>
939- </table>
940-
941- </tal:latest_events>
942-
943-</div>
944-
945-</body>
946+ >
947+ <body>
948+ <div metal:fill-slot="main"
949+ tal:define="latest_karma context/latestKarma">
950+
951+ <p class="application-summary">
952+ This is a summary of the Launchpad karma earned by
953+ <span tal:replace="context/displayname" />, organized by activity
954+ type.
955+ </p>
956+
957+ <p id="no-karma" tal:condition="not: view/has_karma">
958+ <tal:expired condition="view/has_expired_karma">
959+ <tal:name replace="context/displayname" />'s karma has expired.
960+ </tal:expired>
961+ <tal:never condition="not: view/has_expired_karma">
962+ No karma has yet been assigned to
963+ <tal:name replace="context/displayname" />.
964+ Karma is updated daily.
965+ </tal:never>
966+ </p>
967+
968+ <table class="cozy-listing" id="karmapoints"
969+ tal:condition="context/karma_category_caches">
970+ <tbody>
971+ <tr tal:repeat="cache context/karma_category_caches">
972+ <td tal:content="cache/category/title">Bug Management</td>
973+ <td><strong
974+ class=""
975+ tal:attributes="class string: ${cache/category/name}-stat"
976+ tal:content="cache/karmavalue">10</strong></td>
977+ </tr>
978+ </tbody>
979+ </table>
980+ <b>Total karma:</b>
981+ <tal:total_karma tal:replace="context/karma">34</tal:total_karma>
982+
983+ <tal:latest_events condition="latest_karma">
984+ <p class="application-summary"
985+ style="padding-top: 2em;" >
986+ These are the latest actions which have contributed to the Launchpad
987+ karma earned by <span tal:replace="context/displayname" /> and the
988+ dates on which the action occurred.
989+ </p>
990+ <table class="listing sortable" id="karmaactions">
991+ <thead>
992+ <tr>
993+ <th>Date</th>
994+ <th>Action</th>
995+ </tr>
996+ </thead>
997+ <tbody>
998+ <tr tal:repeat="karma latest_karma">
999+ <td>
1000+ <span class="sortkey"
1001+ tal:content="karma/datecreated/fmt:datetime">2005-10-06</span>
1002+ <span
1003+ tal:attributes="title karma/datecreated/fmt:datetime"
1004+ tal:content="karma/datecreated/fmt:approximatedate" />
1005+ </td>
1006+ <td tal:content="karma/action/title">Bug Filed</td>
1007+ </tr>
1008+ </tbody>
1009+ </table>
1010+ </tal:latest_events>
1011+ </div>
1012+ </body>
1013 </html>
1014
1015=== modified file 'lib/lp/registry/templates/product-index.pt'
1016--- lib/lp/registry/templates/product-index.pt 2009-08-31 21:20:55 +0000
1017+++ lib/lp/registry/templates/product-index.pt 2009-09-02 20:40:14 +0000
1018@@ -6,10 +6,6 @@
1019 metal:use-macro="view/macro:page/main_side"
1020 i18n:domain="launchpad">
1021 <body>
1022- <tal:heading metal:fill-slot="heading">
1023- <h1 tal:replace="structure view/title_edit_widget">project title</h1>
1024- </tal:heading>
1025-
1026 <tal:main metal:fill-slot="main"
1027 define="overview_menu context/menu:overview">
1028 <div class="top-portlet">
1029
1030=== added file 'version.txt'
1031--- version.txt 1970-01-01 00:00:00 +0000
1032+++ version.txt 2009-09-01 20:24:17 +0000
1033@@ -0,0 +1,1 @@
1034+2.2.7