Merge lp:~barry/launchpad/413158-people into lp:launchpad

Proposed by Barry Warsaw
Status: Merged
Approved by: Curtis Hovey
Approved revision: no longer in the source branch.
Merged at revision: not available
Proposed branch: lp:~barry/launchpad/413158-people
Merge into: lp:launchpad
Diff against target: None lines
To merge this branch: bzr merge lp:~barry/launchpad/413158-people
Reviewer Review Type Date Requested Status
Curtis Hovey (community) Approve
Review via email: mp+10323@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Barry Warsaw (barry) wrote :
Download full text (3.6 KiB)

 reviewer sinzui

= Summary =

This branch fixes bug 413158, updating the /people top level page to UI 3.0.

== Proposed fix ==

Update the people-index.pt template and clean up the UI as necessary.

== Pre-implementation notes ==

Curtis and Martin have seen mockups of the page and made comments on the
original bug report. Many of these have been addressed but there are still
imperfection in the page. In the interest of time boxing, we'll deal with
those at a later date.

== Implementation details ==

There are still two known imperfections that I won't fix now. First, the
style-3.0.css isn't quite right for the <th> column headers. In style.css,
these headings are red and bold, but in style-3.0.css they are only red,
though they are also slightly larger. The problem is that the thead styles
are reset in style-3.0.css and although I copied the thead styles over, the
font-weight does not seem to work. I don't know why.

The other imperfection is that there is only one navigation menu, even though
it contains a few actions. This is a legitimate case for having two menus,
but I have been unable to get two menus working. The single menu approach
doesn't look horrible, so I consider this acceptable in the interest of time
boxing the work.

I have refactored the calculation of whether a batch navigation has multiple
pages into the base class, and added a test. Previously, this test was in the
branch listing page, but it's of general usefulness and belongs in the base
class.

The /people/+portlet-about text has been moved inline so this portlet was
removed, as was a test that depended on it. I also moved
/people/+portlet-stats inline and removed that portlet.

I refactored the top-level object menu into a mixin. This menu will be shared
with the top-level projects and project-group pages when I update their UI
next.

I renamed FOAFSearchView to PeopleSearchView since we're not really doing FOAF.

lint complained about some duplicate methods in class
BranchListingItemsMixin. In fact, there's no way the shadowed (i.e. earlier)
methods could possibly be called, so I removed them. I'm about to send the
branch through ec2 so we'll see if they broke anything, but I'd be quite
surprised if they did.

I did some other refactoring and cleanup I found along the way.

== Tests ==

bin/test -vv -t batch_navigation.txt -t people-views.txt

== Demo and Q/A ==

 * Visit http://launchpad.dev/people as an admin, a normal user, and not
   logged in. As an admin you'll get a little extra text in the narrative,
   and as an anonymous user you'll get a 'Create an account' action link.
   This was approved by Martin.

 * As a normal user, visit http://launchpad.dev/people and search for 's' to
   see the multi-page batch navigation.

 * As a normal user, visit http://launchpad.dev/people and search for 'test'
   to see the single-page batch navigation.

= Launchpad lint =

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

Linting changed files:
  lib/lp/registry/browser/configure.zcml
  lib/canonical/launchpad/pagetests/basics/notfound-traversals.txt
  lib/lp/registry/browser/m...

Read more...

Revision history for this message
Curtis Hovey (sinzui) wrote :
Download full text (12.5 KiB)

Hi Barry.

This is very good. I know you had concerns about the menus, but I think they
are close to solving the issues in this branch and in the other top-level
collections. I think a subtle renaming of the marker interface will help you
see what I see. I have a code fragment at the bottom of how I would name and
restructure the menus so that you have something ready for your next branches.

...
> === modified file 'lib/canonical/launchpad/icing/style-3-0.css'
> --- lib/canonical/launchpad/icing/style-3-0.css 2009-08-17 11:22:39 +0000
> +++ lib/canonical/launchpad/icing/style-3-0.css 2009-08-17 17:50:02 +0000
> @@ -590,3 +590,10 @@
> word-wrap: break-word;
> }
>
> +/* ==== Listing tables ==== */
> +
> +table.listing thead {
> + color: #9f2b33;

This colour is the bug colour. We need something neutral #333; maybe.

> + font-size: 1.125em;

This font-size is not supported by YUI. We must specify all sizes
using the percentages from the table in this file. try 116% or 123.1%.

...

> === modified file 'lib/canonical/launchpad/webapp/batching.py'
> --- lib/canonical/launchpad/webapp/batching.py 2009-06-25 05:30:52 +0000
> +++ lib/canonical/launchpad/webapp/batching.py 2009-08-14 18:41:41 +0000
> @@ -62,6 +62,10 @@
> def max_batch_size(self):
> return config.launchpad.max_batch_size
>
> + @property
> + def multiple_pages(self):

can you add a docstring? Is has_multiple_pages a better name for what it
returns?

...

> === modified file 'lib/lp/code/browser/branchlisting.py'
> --- lib/lp/code/browser/branchlisting.py 2009-08-13 04:08:38 +0000
> +++ lib/lp/code/browser/branchlisting.py 2009-08-18 14:11:56 +0000
> @@ -331,56 +331,13 @@
> branches = list(self.currentBatch())
> # XXX: TimPenhey 2009-04-08 bug=324546
> # Until there is an API to do this nicely, shove the launchpad.view
> - # permission into the request cache directly. This is done because the
> - # security proxy sometimes causes extra requests to be made.
> + # permission into the request cache directly. This is done because
> + # the security proxy sometimes causes extra requests to be made.
> request = self.view.request
> precache_permission_for_objects(request, 'launchpad.View', branches)
> return branches
>
> @cachedproperty
> - def branch_ids_with_bug_links(self):

I am not sure why this big chunk of code was removed. I this diff lying?

...

> === added file 'lib/lp/registry/browser/menu.py'
> --- lib/lp/registry/browser/menu.py 1970-01-01 00:00:00 +0000
> +++ lib/lp/registry/browser/menu.py 2009-08-18 13:54:51 +0000
> @@ -0,0 +1,40 @@
> +# Copyright 2009 Canonical Ltd. This software is licensed under the
> +# GNU Affero General Public License version 3 (see the file LICENSE).
> +
> +"""Shared menus."""
> +
> +__metaclass__ = type
> +__all__ = [
> + 'TopLevelMenuMixin',
> + ]
> +
> +
> +from canonical.launchpad.webapp.menu import Link
> +
> +
> +class TopLevelMenuMixin:
> + """Menu shared by top level collection objects."""

I think I would prefer this in __init__.py where other shared code
is? Since these collections are in the root of the mainsit...

review: Needs Fixing (code)
Revision history for this message
Barry Warsaw (barry) wrote :
Download full text (35.0 KiB)

On Aug 18, 2009, at 07:11 PM, Curtis Hovey wrote:

>This is very good. I know you had concerns about the menus, but I think they
>are close to solving the issues in this branch and in the other top-level
>collections. I think a subtle renaming of the marker interface will help you
>see what I see. I have a code fragment at the bottom of how I would name and
>restructure the menus so that you have something ready for your next branches.

Yep, it's been a frustrating branch; thanks for talking me off the ledge. Thanks for the review and it's definitely encouraging to see how close this branch puts me to completing the other top level collections.

>...
>> === modified file 'lib/canonical/launchpad/icing/style-3-0.css'
>> --- lib/canonical/launchpad/icing/style-3-0.css 2009-08-17 11:22:39 +0000
>> +++ lib/canonical/launchpad/icing/style-3-0.css 2009-08-17 17:50:02 +0000
>> @@ -590,3 +590,10 @@
>> word-wrap: break-word;
>> }
>>
>> +/* ==== Listing tables ==== */
>> +
>> +table.listing thead {
>> + color: #9f2b33;
>
>This colour is the bug colour. We need something neutral #333; maybe.

I never made the correlation between the heading color and the app color. I thought it was a general heading cue. If not, then I think the default color setting is just fine. You provided the hint for bolding the th in a comment on the bug and that works better, so I'll keep that and remove the color setting.

>> + font-size: 1.125em;
>
>This font-size is not supported by YUI. We must specify all sizes
>using the percentages from the table in this file. try 116% or 123.1%.

116% looks good to me.

>> === modified file 'lib/canonical/launchpad/webapp/batching.py'
>> --- lib/canonical/launchpad/webapp/batching.py 2009-06-25 05:30:52 +0000
>> +++ lib/canonical/launchpad/webapp/batching.py 2009-08-14 18:41:41 +0000
>> @@ -62,6 +62,10 @@
>> def max_batch_size(self):
>> return config.launchpad.max_batch_size
>>
>> + @property
>> + def multiple_pages(self):
>
>can you add a docstring? Is has_multiple_pages a better name for what it
>returns?

Docstring added. 'multiple_pages' was the original name for the property before refactoring, but I agree that 'has_multiple_pages' is a better name. So changed.

>> === modified file 'lib/lp/code/browser/branchlisting.py'
>> --- lib/lp/code/browser/branchlisting.py 2009-08-13 04:08:38 +0000
>> +++ lib/lp/code/browser/branchlisting.py 2009-08-18 14:11:56 +0000
>> @@ -331,56 +331,13 @@
>> branches = list(self.currentBatch())
>> # XXX: TimPenhey 2009-04-08 bug=324546
>> # Until there is an API to do this nicely, shove the launchpad.view
>> - # permission into the request cache directly. This is done because the
>> - # security proxy sometimes causes extra requests to be made.
>> + # permission into the request cache directly. This is done because
>> + # the security proxy sometimes causes extra requests to be made.
>> request = self.view.request
>> precache_permission_for_objects(request, 'launchpad.View', branches)
>> return branches
>>
>> @cachedproperty
>> - def branch_ids_with_bug_links(self):
>
>I am not...

Revision history for this message
Curtis Hovey (sinzui) wrote :
Download full text (8.2 KiB)

Hi Barry.

This looks good. I see have a small concern about one menu and I think
some of the links need to be handled differently.

On Wed, 2009-08-19 at 00:03 +0000, Barry Warsaw wrote:
...

>> + def meetings(self):
> >> + return Link('/sprints/', 'View meetings', icon='info')
> >
> >Seeing this really make me think we need to separate sprints from
> >blueprints. I am certain this link belongs to the mainsite (which
> >is registry)
>
> mainsite seems reasonable. Is there something you want me to change here?
>
No. This a remark. I recently realised how these were implement and I was offended. I think this is a great feature that is hard to find and use. Tim did not know this existed until I showed him yesterday.

...

>> === modified file 'lib/lp/registry/browser/person.py'
> >> --- lib/lp/registry/browser/person.py 2009-08-03 15:21:35 +0000
> >> +++ lib/lp/registry/browser/person.py 2009-08-18 15:28:28 +0000
>
...

> implements(IRegistryCollectionNavigationMenu)
> >
> >So you have one marker interface, and one implementation below that
> >is used in 5 collections. Your other branches will be much smaller.
> >I think this is what you intend to do placed the like definitions in
> >a shared place.
>
> Well, now I have my justification for menu.py! :)
>
I think you are correct.

...

>These look like the menus we want to build.
> >
> >{{{
> >
> >class PersonSetNavigationMenu(NavigationMenu):
> > """Navigation menu for PersonSet actions"""
> > usedfor = IPersonSet
> > links = [
> > register_team','adminpeoplemerge', 'adminteammerge', 'mergeaccounts']
> >
> >
> >class IRegistryCollectionNavigationMenu(Interface):
> > """Marker interface for the registry collection navigation menu."""
> >
> >
> >class RegistryCollectionNavigationMenu(NavigationMenu, TopLevelMenuMixin):
> > """Navigation menu for registry person search collection."""
> > usedfor = IRegistryCollectionNavigationMenu
> > facet = 'overview'
> > links = ['products', 'distributions', 'people', 'meetings']
> >
> >
> >class PeopleSearchView(LaunchpadView):
> > """Search for people and teams on the /people page."""
> > implements(IRegistryCollectionNavigationMenu)
> >
> >}}}
>
> Nice. This works but with a small ugliness. Because `usedfor` in the action
> menu must correspond to the context object's interface, it can't be completely
> generalized. It will force each top level collection view to create a
> subclass containing only the `usedfor` attribute, set to the right interface.
> Icky, but close enough for jazz.
>
No. I do not see that. We created the one shared menu (RegistryCollectionNavigationMenu). All the views (that already exist) just need to add the implements.

I am not sure of the value of RegistryCollectionActionMenuBase. I do not think those links are usable between collections. Those links belong to the IPersonSet only. You might as well move those links to the one true implementing class PersonSetNavigationMenu

Doing this does feel like a hack because it would be better to register both
> menus on the view instead of one on the view and one on the context object.
> But it's a hack that will live another day be...

Read more...

review: Needs Fixing (code)
Revision history for this message
Barry Warsaw (barry) wrote :
Download full text (12.1 KiB)

On Aug 19, 2009, at 07:30 PM, Curtis Hovey wrote:

>This looks good. I see have a small concern about one menu and I think
>some of the links need to be handled differently.

Okay. Hopefully we're close!

>>These look like the menus we want to build.
>> >
>> >{{{
>> >
>> >class PersonSetNavigationMenu(NavigationMenu):
>> > """Navigation menu for PersonSet actions"""
>> > usedfor = IPersonSet
>> > links = [
>> > register_team','adminpeoplemerge', 'adminteammerge', 'mergeaccounts']
>> >
>> >
>> >class IRegistryCollectionNavigationMenu(Interface):
>> > """Marker interface for the registry collection navigation menu."""
>> >
>> >
>> >class RegistryCollectionNavigationMenu(NavigationMenu, TopLevelMenuMixin):
>> > """Navigation menu for registry person search collection."""
>> > usedfor = IRegistryCollectionNavigationMenu
>> > facet = 'overview'
>> > links = ['products', 'distributions', 'people', 'meetings']
>> >
>> >
>> >class PeopleSearchView(LaunchpadView):
>> > """Search for people and teams on the /people page."""
>> > implements(IRegistryCollectionNavigationMenu)
>> >
>> >}}}
>>
>> Nice. This works but with a small ugliness. Because `usedfor` in the action
>> menu must correspond to the context object's interface, it can't be completely
>> generalized. It will force each top level collection view to create a
>> subclass containing only the `usedfor` attribute, set to the right interface.
>> Icky, but close enough for jazz.
>>
>No. I do not see that. We created the one shared menu (RegistryCollectionNavigationMenu). All the views (that already exist) just need to add the implements.
>
>I am not sure of the value of RegistryCollectionActionMenuBase. I do not think those links are usable between collections. Those links belong to the IPersonSet only. You might as well move those links to the one true implementing class PersonSetNavigationMenu

I'm not so sure about that. Certainly, the admin_merge_* links pertain only to IPersonSet, but that's easily controlled by setting the 'links' attribute. In fact, that's exactly the refactoring I've done in my /projects branch, so I don't want to do it here. But I do think the register_projects and register_teams link make sense on all the top level collections, and I don't think we need to filter that further for the context. Also, create_account when anonymous makes sense on all the collections, so I think that justifies RegistryCollectionActionMenuBase.

>
>Doing this does feel like a hack because it would be better to register both
>> menus on the view instead of one on the view and one on the context object.
>> But it's a hack that will live another day because I'm not going to worry
>> about it any more. :)
>>
>Creating a base that has only one user is a waste, remove it.

It won't be a waste when you see the /projects branch.

>> === modified file 'lib/lp/registry/browser/menu.py'
>> --- lib/lp/registry/browser/menu.py 2009-08-18 13:54:51 +0000
>> +++ lib/lp/registry/browser/menu.py 2009-08-18 23:27:23 +0000
>
>...
>
>> @@ -27,14 +34,72 @@
>> def meetings(self):
>> return Link('/sprints/', 'View meetings', icon='info')
>>
>> +...

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

Hi barry.

Your changes look great. I accept you arguments to keep the base menu.

  review approve

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/launchpad/doc/batch_navigation.txt'
2--- lib/canonical/launchpad/doc/batch_navigation.txt 2009-03-24 12:43:49 +0000
3+++ lib/canonical/launchpad/doc/batch_navigation.txt 2009-08-14 18:41:41 +0000
4@@ -1,4 +1,6 @@
5-= Batch Navigation =
6+================
7+Batch Navigation
8+================
9
10 Most of the test for this behavior is in the lazr.batchnavigation package.
11
12@@ -44,10 +46,12 @@
13 >>> reindeer = ['Dasher', 'Dancer', 'Prancer', 'Vixen', 'Comet',
14 ... 'Cupid', 'Donner', 'Blitzen', 'Rudolph']
15
16-== Performance with SQLObject ==
17-
18-This section demonstrates that batching generates sensible SQL queries when used
19-with SQLObject, i.e. that it puts appropriate LIMIT clauses on queries.
20+
21+Performance with SQLObject
22+==========================
23+
24+This section demonstrates that batching generates sensible SQL queries when
25+used with SQLObject, i.e. that it puts appropriate LIMIT clauses on queries.
26
27 Imports:
28
29@@ -58,8 +62,7 @@
30 Prepare a query, and create a batch of the results:
31
32 >>> select_results = EmailAddress.select(orderBy='id')
33- >>> batch_nav = BatchNavigator(select_results, build_request(),
34- ... size=10)
35+ >>> batch_nav = BatchNavigator(select_results, build_request(), size=10)
36 >>> email_batch = batch_nav.currentBatch()
37 >>> batch_items = list(email_batch)
38
39@@ -85,7 +88,17 @@
40 True
41
42
43-== Maximum batch size ==
44+Multiple pages
45+==============
46+
47+The batch navigator tells us whether multiple pages will be used.
48+
49+ >>> batch_nav.multiple_pages
50+ True
51+
52+
53+Maximum batch size
54+==================
55
56 Since the batch size is exposed in the URL, it's possible for users to
57 tweak the batch parameter to retrieve more results. Since that may
58@@ -108,7 +121,8 @@
59 >>> ignored = config.pop('max-batch-size')
60
61
62-== Batch views ==
63+Batch views
64+===========
65
66 A view is often used with a BatchNavigator to determine when to
67 display the current batch.
68
69=== modified file 'lib/canonical/launchpad/icing/style-3-0.css'
70--- lib/canonical/launchpad/icing/style-3-0.css 2009-08-17 11:22:39 +0000
71+++ lib/canonical/launchpad/icing/style-3-0.css 2009-08-17 17:50:02 +0000
72@@ -590,3 +590,10 @@
73 word-wrap: break-word;
74 }
75
76+/* ==== Listing tables ==== */
77+
78+table.listing thead {
79+ color: #9f2b33;
80+ font-size: 1.125em;
81+ font-weight: bold;
82+}
83
84=== modified file 'lib/canonical/launchpad/pagetests/basics/notfound-traversals.txt'
85--- lib/canonical/launchpad/pagetests/basics/notfound-traversals.txt 2009-08-13 19:20:04 +0000
86+++ lib/canonical/launchpad/pagetests/basics/notfound-traversals.txt 2009-08-17 17:50:02 +0000
87@@ -368,7 +368,6 @@
88 >>> check("/people/")
89 >>> check("/people/+requestmerge", auth=True)
90 >>> check_redirect("/people/+mergerequest-sent", auth=True)
91->>> check("/people/+portlet-about")
92
93 This is for a team:
94
95
96=== modified file 'lib/canonical/launchpad/webapp/batching.py'
97--- lib/canonical/launchpad/webapp/batching.py 2009-06-25 05:30:52 +0000
98+++ lib/canonical/launchpad/webapp/batching.py 2009-08-14 18:41:41 +0000
99@@ -62,6 +62,10 @@
100 def max_batch_size(self):
101 return config.launchpad.max_batch_size
102
103+ @property
104+ def multiple_pages(self):
105+ return self.batch.total() > self.batch.size
106+
107
108 class TableBatchNavigator(BatchNavigator):
109 """See canonical.launchpad.interfaces.ITableBatchNavigator."""
110
111=== modified file 'lib/lp/code/browser/branchlisting.py'
112--- lib/lp/code/browser/branchlisting.py 2009-08-13 04:08:38 +0000
113+++ lib/lp/code/browser/branchlisting.py 2009-08-18 14:11:56 +0000
114@@ -331,56 +331,13 @@
115 branches = list(self.currentBatch())
116 # XXX: TimPenhey 2009-04-08 bug=324546
117 # Until there is an API to do this nicely, shove the launchpad.view
118- # permission into the request cache directly. This is done because the
119- # security proxy sometimes causes extra requests to be made.
120+ # permission into the request cache directly. This is done because
121+ # the security proxy sometimes causes extra requests to be made.
122 request = self.view.request
123 precache_permission_for_objects(request, 'launchpad.View', branches)
124 return branches
125
126 @cachedproperty
127- def branch_ids_with_bug_links(self):
128- """Return a set of branch ids that should show bug badges."""
129- bug_branches = getUtility(IBugBranchSet).getBugBranchesForBranches(
130- self._branches_for_current_batch, self.view.user)
131- return set(bug_branch.branch.id for bug_branch in bug_branches)
132-
133- @cachedproperty
134- def branch_ids_with_spec_links(self):
135- """Return a set of branch ids that should show blueprint badges."""
136- spec_branches = getUtility(
137- ISpecificationBranchSet).getSpecificationBranchesForBranches(
138- self._branches_for_current_batch, self.view.user)
139- return set(spec_branch.branch.id for spec_branch in spec_branches)
140-
141- @cachedproperty
142- def branch_ids_with_merge_proposals(self):
143- """Return a set of branches that should show merge proposal badges.
144-
145- Branches have merge proposals badges if they've been proposed for
146- merging into another branch (source branches).
147- """
148- branches = self.view._getCollection().visibleByUser(self.view.user)
149- proposals = branches.getMergeProposals(
150- for_branch=self._branch_for_current_batch)
151- return set(proposal.source_branch.id for proposal in proposals)
152-
153- @cachedproperty
154- def tip_revisions(self):
155- """Return a set of branch ids that should show blueprint badges."""
156- revisions = getUtility(IRevisionSet).getTipRevisionsForBranches(
157- self._branches_for_current_batch)
158- if revisions is None:
159- revision_map = {}
160- else:
161- # Key the revisions by revision id.
162- revision_map = dict((revision.revision_id, revision)
163- for revision in revisions)
164-
165- # Return a dict keyed on branch id.
166- return dict((branch.id, revision_map.get(branch.last_scanned_id))
167- for branch in self._branches_for_current_batch)
168-
169- @cachedproperty
170 def product_series_map(self):
171 """Return a map of branch id to a list of product series.
172
173@@ -526,10 +483,6 @@
174 """Return a list of BranchListingItems."""
175 return self.decoratedBranches(self.visible_branches_for_view)
176
177- @cachedproperty
178- def multiple_pages(self):
179- return self.batch.total() > self.batch.size
180-
181 @property
182 def table_class(self):
183 # XXX: MichaelHudson 2007-10-18 bug=153894: This means there are two
184
185=== modified file 'lib/lp/registry/browser/configure.zcml'
186--- lib/lp/registry/browser/configure.zcml 2009-08-14 00:52:28 +0000
187+++ lib/lp/registry/browser/configure.zcml 2009-08-18 13:54:51 +0000
188@@ -794,18 +794,20 @@
189 <browser:menus
190 module="lp.registry.browser.person"
191 classes="
192+ PeopleSearchNavigationMenu
193+ PersonBugsMenu
194+ PersonEditNavigationMenu
195 PersonFacets
196 PersonOverviewMenu
197- TeamOverviewMenu
198- PersonBugsMenu
199+ PersonOverviewNavigationMenu
200+ PersonRelatedSoftwareNavigationMenu
201+ PersonSetContextMenu
202 PersonSpecsMenu
203 TeamBugsMenu
204+ TeamOverviewMenu
205+ TeamOverviewNavigationMenu
206 TeamSpecsMenu
207- PersonSetContextMenu
208- PersonOverviewNavigationMenu
209- PersonEditNavigationMenu
210- PersonRelatedSoftwareNavigationMenu
211- TeamOverviewNavigationMenu"/>
212+ "/>
213 <browser:url
214 for="lp.registry.interfaces.person.IPerson"
215 path_expression="string:~${name}"
216@@ -1242,13 +1244,10 @@
217 <browser:pages
218 for="lp.registry.interfaces.person.IPersonSet"
219 permission="zope.Public"
220- class="lp.registry.browser.person.FOAFSearchView">
221+ class="lp.registry.browser.person.PeopleSearchView">
222 <browser:page
223 name="+index"
224 template="../templates/people-index.pt"/>
225- <browser:page
226- name="+portlet-stats"
227- template="../templates/people-portlet-stats.pt"/>
228 </browser:pages>
229 <browser:page
230 name="+requestmerge"
231@@ -1284,13 +1283,6 @@
232 name="+mergerequest-sent"
233 template="../templates/people-mergerequest-sent.pt"/>
234 </browser:pages>
235- <browser:pages
236- for="lp.registry.interfaces.person.IPersonSet"
237- permission="zope.Public">
238- <browser:page
239- name="+portlet-about"
240- template="../templates/people-portlet-about.pt"/>
241- </browser:pages>
242 </facet>
243 <browser:navigation
244 module="lp.registry.browser.milestone"
245
246=== added file 'lib/lp/registry/browser/menu.py'
247--- lib/lp/registry/browser/menu.py 1970-01-01 00:00:00 +0000
248+++ lib/lp/registry/browser/menu.py 2009-08-18 13:54:51 +0000
249@@ -0,0 +1,40 @@
250+# Copyright 2009 Canonical Ltd. This software is licensed under the
251+# GNU Affero General Public License version 3 (see the file LICENSE).
252+
253+"""Shared menus."""
254+
255+__metaclass__ = type
256+__all__ = [
257+ 'TopLevelMenuMixin',
258+ ]
259+
260+
261+from canonical.launchpad.webapp.menu import Link
262+
263+
264+class TopLevelMenuMixin:
265+ """Menu shared by top level collection objects."""
266+
267+ def products(self):
268+ return Link('/projects/', 'View projects', icon='info')
269+
270+ def distributions(self):
271+ return Link('/distros/', 'View distributions', icon='info')
272+
273+ def people(self):
274+ return Link('/people/', 'View people', icon='info')
275+
276+ def meetings(self):
277+ return Link('/sprints/', 'View meetings', icon='info')
278+
279+ def register_project(self):
280+ text = 'Register a project'
281+ return Link('/projects/+new', text, icon='add')
282+
283+ def register_team(self):
284+ text = 'Register a team'
285+ return Link('/people/+newteam', text, icon='add')
286+
287+ def create_account(self):
288+ text = 'Create an account'
289+ return Link('/people/+login', text, icon='add')
290
291=== modified file 'lib/lp/registry/browser/person.py'
292--- lib/lp/registry/browser/person.py 2009-08-03 15:21:35 +0000
293+++ lib/lp/registry/browser/person.py 2009-08-18 15:28:28 +0000
294@@ -10,11 +10,12 @@
295 __all__ = [
296 'BeginTeamClaimView',
297 'BugSubscriberPackageBugsSearchListingView',
298- 'FOAFSearchView',
299 'EmailToPersonView',
300+ 'PeopleSearchMenu'
301+ 'PeopleSearchView',
302 'PersonAccountAdministerView',
303+ 'PersonAddView',
304 'PersonAdministerView',
305- 'PersonAddView',
306 'PersonAnswerContactForView',
307 'PersonAnswersMenu',
308 'PersonAssignedBugTaskSearchListingView',
309@@ -30,10 +31,10 @@
310 'PersonEditHomePageView',
311 'PersonEditIRCNicknamesView',
312 'PersonEditJabberIDsView',
313+ 'PersonEditLocationView',
314 'PersonEditSSHKeysView',
315 'PersonEditView',
316 'PersonEditWikiNamesView',
317- 'PersonEditLocationView',
318 'PersonFacets',
319 'PersonGPGView',
320 'PersonIndexView',
321@@ -42,8 +43,8 @@
322 'PersonNavigation',
323 'PersonOAuthTokensView',
324 'PersonOverviewMenu',
325+ 'PersonRdfContentsView',
326 'PersonRdfView',
327- 'PersonRdfContentsView',
328 'PersonRelatedBugTaskSearchListingView',
329 'PersonRelatedSoftwareView',
330 'PersonReportedBugTaskSearchListingView',
331@@ -51,9 +52,9 @@
332 'PersonSetContextMenu',
333 'PersonSetNavigation',
334 'PersonSpecFeedbackView',
335+ 'PersonSpecWorkloadTableView',
336+ 'PersonSpecWorkloadView',
337 'PersonSpecsMenu',
338- 'PersonSpecWorkloadView',
339- 'PersonSpecWorkloadTableView',
340 'PersonSubscribedBugTaskSearchListingView',
341 'PersonView',
342 'PersonVouchersView',
343@@ -66,14 +67,14 @@
344 'SearchNeedAttentionQuestionsView',
345 'SearchSubscribedQuestionsView',
346 'TeamAddMyTeamsView',
347+ 'TeamBreadcrumbBuilder',
348 'TeamEditLocationView',
349 'TeamJoinView',
350- 'TeamBreadcrumbBuilder',
351 'TeamLeaveView',
352+ 'TeamMembershipView',
353+ 'TeamMugshotView',
354 'TeamNavigation',
355 'TeamOverviewMenu',
356- 'TeamMembershipView',
357- 'TeamMugshotView',
358 'TeamReassignmentView',
359 'TeamSpecsMenu',
360 'archive_to_person',
361@@ -198,6 +199,7 @@
362 from canonical.launchpad.browser.branding import BrandingChangeView
363 from lp.registry.browser.mailinglists import (
364 enabled_with_active_mailing_list)
365+from lp.registry.browser.menu import TopLevelMenuMixin
366 from lp.answers.browser.questiontarget import SearchQuestionsView
367
368 from canonical.launchpad.fields import LocationField
369@@ -627,29 +629,14 @@
370 canonical_url(me, request=self.request), status=303)
371
372
373-class PersonSetContextMenu(ContextMenu):
374+class PersonSetContextMenu(ContextMenu, TopLevelMenuMixin):
375
376 usedfor = IPersonSet
377
378- links = ['products', 'distributions', 'people', 'meetings', 'newteam',
379+ links = ['products', 'distributions', 'people', 'meetings',
380+ 'register_team',
381 'adminpeoplemerge', 'adminteammerge', 'mergeaccounts']
382
383- def products(self):
384- return Link('/projects/', 'View projects')
385-
386- def distributions(self):
387- return Link('/distros/', 'View distributions')
388-
389- def people(self):
390- return Link('/people/', 'View people')
391-
392- def meetings(self):
393- return Link('/sprints/', 'View meetings')
394-
395- def newteam(self):
396- text = 'Register a team'
397- return Link('+newteam', text, icon='add')
398-
399 def mergeaccounts(self):
400 text = 'Merge accounts'
401 return Link('+requestmerge', text, icon='edit')
402@@ -1348,35 +1335,64 @@
403 return self.proposed_memberships or self.invited_memberships
404
405
406-class FOAFSearchView:
407+class IPeopleSearchNavigationMenu(Interface):
408+ """Marker interface for people search navigation menu."""
409+
410+
411+class PeopleSearchNavigationMenu(NavigationMenu, TopLevelMenuMixin):
412+ """Navigation menu for people search."""
413+
414+ usedfor = IPeopleSearchNavigationMenu
415+ facet = 'overview'
416+
417+ links = ['products', 'distributions', 'people', 'meetings',
418+ 'register_team', 'register_project']
419+
420+ def initialize(self):
421+ """See `MenuBase`."""
422+ if self.context.user is None:
423+ # Make a copy of the links so as not to permanently mutate the
424+ # class attribute.
425+ self.links = PeopleSearchNavigationMenu.links[:]
426+ self.links.append('create_account')
427+
428+
429+class PeopleSearchView(LaunchpadView):
430+ """Search for people and teams on the /people page."""
431+
432+ implements(IPeopleSearchNavigationMenu)
433
434 def __init__(self, context, request):
435- self.context = context
436- self.request = request
437+ super(PeopleSearchView, self).__init__(context, request)
438 self.results = []
439
440- def teamsCount(self):
441- return getUtility(IPersonSet).teamsCount()
442+ @property
443+ def number_of_people(self):
444+ return self.context.peopleCount()
445
446- def peopleCount(self):
447- return getUtility(IPersonSet).peopleCount()
448+ @property
449+ def number_of_teams(self):
450+ return self.context.teamsCount()
451
452 def searchPeopleBatchNavigator(self):
453 name = self.request.get("name")
454-
455 if not name:
456 return None
457-
458 searchfor = self.request.get("searchfor")
459 if searchfor == "peopleonly":
460- results = getUtility(IPersonSet).findPerson(name)
461+ results = self.context.findPerson(name)
462 elif searchfor == "teamsonly":
463- results = getUtility(IPersonSet).findTeam(name)
464+ results = self.context.findTeam(name)
465 else:
466- results = getUtility(IPersonSet).find(name)
467-
468+ results = self.context.find(name)
469 return BatchNavigator(results, self.request)
470
471+ @property
472+ def is_admin(self):
473+ """Is the logged in user a Launchpad administrator?"""
474+ return (self.user is not None and
475+ self.user.inTeam(getUtility(ILaunchpadCelebrities).admin))
476+
477
478 class PersonAddView(LaunchpadFormView):
479 """The page where users can create new Launchpad profiles."""
480
481=== modified file 'lib/lp/registry/browser/product.py'
482--- lib/lp/registry/browser/product.py 2009-08-12 13:20:10 +0000
483+++ lib/lp/registry/browser/product.py 2009-08-18 13:54:51 +0000
484@@ -82,6 +82,7 @@
485 from lp.bugs.browser.bugtask import (
486 BugTargetTraversalMixin, get_buglisting_search_filter_url)
487 from lp.registry.browser.distribution import UsesLaunchpadMixin
488+from lp.registry.browser.menu import TopLevelMenuMixin
489 from lp.answers.browser.faqtarget import FAQTargetNavigationMixin
490 from canonical.launchpad.browser.feeds import FeedsMixin
491 from lp.registry.browser.productseries import get_series_branch_error
492@@ -546,40 +547,17 @@
493 enable_only = ['overview']
494
495
496-class ProductSetContextMenu(ContextMenu):
497+class ProductSetContextMenu(ContextMenu, TopLevelMenuMixin):
498
499 usedfor = IProductSet
500
501 links = ['products', 'distributions', 'people', 'meetings',
502- 'all', 'register', 'register_team', 'review_licenses']
503-
504- def register(self):
505- text = 'Register a project'
506- # We link to the guided form, though people who know the URL can
507- # just jump to +new directly. That might be considered a
508- # feature!
509- return Link('+new', text, icon='add')
510-
511- def register_team(self):
512- text = 'Register a team'
513- return Link('/people/+newteam', text, icon='add')
514+ 'all', 'register_project', 'register_team', 'review_licenses']
515
516 def all(self):
517 text = 'List all projects'
518 return Link('+all', text, icon='list')
519
520- def products(self):
521- return Link('/projects/', 'View projects')
522-
523- def distributions(self):
524- return Link('/distros/', 'View distributions')
525-
526- def people(self):
527- return Link('/people/', 'View people')
528-
529- def meetings(self):
530- return Link('/sprints/', 'View meetings')
531-
532 @enabled_with_permission('launchpad.ProjectReview')
533 def review_licenses(self):
534 return Link('+review-licenses', 'Review projects')
535
536=== added file 'lib/lp/registry/browser/tests/people-views.txt'
537--- lib/lp/registry/browser/tests/people-views.txt 1970-01-01 00:00:00 +0000
538+++ lib/lp/registry/browser/tests/people-views.txt 2009-08-14 17:31:10 +0000
539@@ -0,0 +1,70 @@
540+==============================
541+Searching for people and teams
542+==============================
543+
544+The view behind the /people page provides searching for people and teams. The
545+view knows how many people and teams are currently registered in Launchpad.
546+
547+ >>> from lp.registry.interfaces.person import IPersonSet
548+ >>> login('foo.bar@canonical.com')
549+ >>> person_set = getUtility(IPersonSet)
550+
551+ >>> view = create_initialized_view(person_set, '+index')
552+ >>> view.number_of_people
553+ 48
554+ >>> view.number_of_teams
555+ 17
556+
557+The view also knows whether the logged in user, such as Foo Bar is a Launchpad
558+administrator or not.
559+
560+ >>> view.is_admin
561+ True
562+
563+But the anonymous user is not an administrator.
564+
565+ >>> logout()
566+ >>> view = create_initialized_view(person_set, '+index')
567+ >>> view.is_admin
568+ False
569+
570+Sample Person is also not an administrator.
571+
572+ >>> login('test@canonical.com')
573+ >>> view = create_initialized_view(person_set, '+index')
574+ >>> view.is_admin
575+ False
576+
577+
578+Batch navigator
579+===============
580+
581+The view returns a batch navigator for searching through sets of teams,
582+people, or both. By default, both are searched for the given name. There is
583+one person and one team matching the 'test' string.
584+
585+ >>> from zope.security.proxy import removeSecurityProxy
586+ >>> def print_batch(batch):
587+ ... for thing in batch.currentBatch():
588+ ... naked_thing = removeSecurityProxy(thing)
589+ ... print naked_thing
590+
591+ >>> form = dict(name='test')
592+ >>> view = create_initialized_view(person_set, '+index', form=form)
593+ >>> print_batch(view.searchPeopleBatchNavigator())
594+ <Person at ... name12 (Sample Person)>
595+ <Person at ... testing-spanish-team (testing Spanish team)>
596+
597+Searching for just people returns Sample Person.
598+
599+ >>> form['searchfor'] = 'peopleonly'
600+ >>> view = create_initialized_view(person_set, '+index', form=form)
601+ >>> print_batch(view.searchPeopleBatchNavigator())
602+ <Person at ... name12 (Sample Person)>
603+
604+Searching for just teams returns the testing Spanish team.
605+
606+ >>> form['searchfor'] = 'teamsonly'
607+ >>> view = create_initialized_view(person_set, '+index', form=form)
608+ >>> print_batch(view.searchPeopleBatchNavigator())
609+ <Person at ... testing-spanish-team (testing Spanish team)>
610
611=== modified file 'lib/lp/registry/templates/people-index.pt'
612--- lib/lp/registry/templates/people-index.pt 2009-07-17 17:59:07 +0000
613+++ lib/lp/registry/templates/people-index.pt 2009-08-18 14:16:16 +0000
614@@ -6,39 +6,36 @@
615 xml:lang="en"
616 lang="en"
617 dir="ltr"
618- metal:use-macro="view/macro:page/pillarindex"
619+ metal:use-macro="view/macro:page/main_side"
620 i18n:domain="launchpad"
621 >
622 <body>
623 <h1 metal:fill-slot="heading">People and teams</h1>
624
625- <metal:leftportlets fill-slot="portlets_one">
626- <div tal:replace="structure context/@@+portlet-about" />
627- </metal:leftportlets>
628-
629- <metal:rightportlets fill-slot="portlets_two">
630- <div tal:replace="structure context/@@+portlet-stats" />
631- </metal:rightportlets>
632-
633- <h1 metal:fill-slot="application-heading">People and teams</h1>
634+ <tal:side metal:fill-slot="side">
635+ <tal:menu replace="structure view/@@+global-actions" />
636+ </tal:side>
637
638 <div metal:fill-slot="main">
639- <tal:block define="bn view/searchPeopleBatchNavigator">
640- <div align="center">
641+ There are currently <span tal:replace="view/number_of_people" /> people
642+ and <span tal:replace="view/number_of_teams" /> teams registered in
643+ Launchpad.
644+ <tal:block define="batch view/searchPeopleBatchNavigator">
645+ <div>
646 <form class="central" name="search" action="." method="GET">
647 <table style="margin-bottom: 1em;">
648 <tr>
649 <td>
650- <input type="text" name="name" size="35"
651+ <input type="text" name="name" size="50"
652 tal:attributes="value request/name|nothing"
653 />
654 <input
655- tal:condition="not: bn"
656+ tal:condition="not: batch"
657 type="submit"
658 value="Search"
659 />
660 <input
661- tal:condition="bn"
662+ tal:condition="batch"
663 type="submit"
664 value="Search Again"
665 />
666@@ -110,57 +107,57 @@
667 // --></script>
668 </div>
669
670- <tal:block condition="not: bn">
671- <p id="application-summary">
672- Launchpad may create <b>duplicate profiles</b> for you
673- as it automatically reviews translation files, mailing lists and
674- other forums. If you find you have more than one profile here,
675- perhaps for multiple email addresses, you can
676- <a href="+requestmerge">merge the duplicate accounts</a>.
677- </p>
678+ <tal:block condition="not: batch">
679+ <div id="application-summary">
680+ <p>Launchpad creates
681+ <a href="https://help.launchpad.net/YourAccount">profiles</a> for
682+ people based on Launchpad usage, as well as information collected
683+ from public sources such as bug trackers, mailing lists, public
684+ key servers, and published application translations. You can
685+ <a href="+me">manage your account</a> in Launchpad, and you can
686+ <a href="+newteam">create teams of people</a> for organizing
687+ around common interests, discussions or permissions.</p>
688+
689+ <p>Launchpad may create
690+ <a href="https://help.launchpad.net/YourAccount/Merging">duplicate
691+ profiles</a> for you as it automatically collects information from
692+ public sources. If you find you have more than one profile on
693+ Launchpad, you can <a href="+requestmerge">request a merge</a> of
694+ the duplicate profiles.</p>
695+
696+ <p tal:condition="view/is_admin">As a Launchpad administrator, you
697+ can also service
698+ <a href="+adminpeoplemerge">person merge requests</a> and
699+ <a href="+adminteammerge">team merge requests</a>.
700+ </p>
701+ </div>
702 </tal:block>
703- <tal:block condition="bn">
704- <tal:block
705- define="prev_url bn/prevBatchURL;
706- next_url bn/nextBatchURL;"
707- >
708- <table class="listing">
709- <thead class="results">
710- <tr tal:condition="python:prev_url or next_url">
711- <td colspan="3">
712- <div style="float: right; text-align: right;">
713- <tal:block condition="prev_url">
714- <a tal:attributes="href prev_url">Previous</a>
715- </tal:block>
716- <tal:block repeat="page_link bn/batchPageURLs">
717- <a tal:attributes="href python:page_link.values()[0]"
718- tal:content="python:page_link.keys()[0]"></a>
719- </tal:block>
720- <tal:block condition="next_url" >
721- <a tal:attributes="href next_url">Next</a>
722- </tal:block>
723- </div>
724- </td>
725- </tr>
726- <tr>
727- <th colspan="2">
728- Name
729- </th>
730- <th>
731- Karma
732- </th>
733- </tr>
734- </thead>
735- <tbody>
736- <tr tal:repeat="person bn/currentBatch">
737- <td><a tal:content="person/displayname"
738- tal:attributes="href person/fmt:url"/>
739- </td>
740- <td tal:content="person/name">foobar</td>
741- <td class="amount" tal:content="person/karma">34</td>
742- </tr>
743- </tbody>
744- </table>
745+ <tal:block condition="batch">
746+ <tal:block condition="batch/multiple_pages">
747+ <tal:navigation
748+ replace="structure batch/@@+navigation-links-upper"/>
749+ </tal:block>
750+ <table class="listing">
751+ <thead>
752+ <tr>
753+ <th>Name</th>
754+ <th>Launchpad ID</th>
755+ <th>Karma</th>
756+ </tr>
757+ </thead>
758+ <tbody>
759+ <tr tal:repeat="person batch/currentBatch">
760+ <td><a tal:content="person/displayname"
761+ tal:attributes="href person/fmt:url"/>
762+ </td>
763+ <td tal:content="person/name">foobar</td>
764+ <td class="amount" tal:content="person/karma">34</td>
765+ </tr>
766+ </tbody>
767+ </table>
768+ <tal:block condition="batch/multiple_pages">
769+ <tal:navigation
770+ replace="structure batch/@@+navigation-links-upper"/>
771 </tal:block>
772 </tal:block>
773 </tal:block>
774
775=== removed file 'lib/lp/registry/templates/people-portlet-about.pt'
776--- lib/lp/registry/templates/people-portlet-about.pt 2009-07-17 17:59:07 +0000
777+++ lib/lp/registry/templates/people-portlet-about.pt 1970-01-01 00:00:00 +0000
778@@ -1,22 +0,0 @@
779-<tal:root
780- xmlns:tal="http://xml.zope.org/namespaces/tal"
781- xmlns:metal="http://xml.zope.org/namespaces/metal"
782- xmlns:i18n="http://xml.zope.org/namespaces/i18n"
783- omit-tag="">
784-<div class="portlet" id="portlet-about">
785-
786- <h2>About these profiles</h2>
787-
788- <div class="portletBody portletContent">
789-
790- <img alt="" src="/@@/info" /> Launchpad profiles are based on actual
791- usage of Launchpad, as well as information available from public sources
792- such as bug trackers, mailing lists, public key servers and published
793- application translations. You can manage your account in the Launchpad,
794- and you can create teams of people who can be maintainers, translators,
795- members, clubs... almost any kind of grouping you need.
796-
797- </div>
798-
799-</div>
800-</tal:root>
801
802=== removed file 'lib/lp/registry/templates/people-portlet-stats.pt'
803--- lib/lp/registry/templates/people-portlet-stats.pt 2009-07-17 17:59:07 +0000
804+++ lib/lp/registry/templates/people-portlet-stats.pt 1970-01-01 00:00:00 +0000
805@@ -1,19 +0,0 @@
806-<tal:root
807- xmlns:tal="http://xml.zope.org/namespaces/tal"
808- xmlns:metal="http://xml.zope.org/namespaces/metal"
809- xmlns:i18n="http://xml.zope.org/namespaces/i18n"
810- omit-tag="">
811-<div class="portlet" id="portlet-stats">
812-
813- <h2>Statistics</h2>
814-
815- <div class="portletBody portletContent">
816-
817- There are currently <span tal:replace="view/peopleCount" /> people
818- and <span tal:replace="view/teamsCount" /> teams registered in
819- Launchpad.
820-
821- </div>
822-
823-</div>
824-</tal:root>