Merge lp:~barry/launchpad/416497-distros into lp:launchpad

Proposed by Barry Warsaw
Status: Merged
Merged at revision: not available
Proposed branch: lp:~barry/launchpad/416497-distros
Merge into: lp:launchpad
Diff against target: None lines
To merge this branch: bzr merge lp:~barry/launchpad/416497-distros
Reviewer Review Type Date Requested Status
Abel Deuring (community) Approve
Review via email: mp+10498@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Barry Warsaw (barry) wrote :

= Summary =

Bug 416497 describes changes to make the top level /distros collection page UI
3.0 worthy.

== Proposed fix ==

Fiddle with the template, add a nav menu, do some clean up. This is the
fourth such top level collection page I've converted.

== Pre-implementation notes ==

None really, since I'm getting better at this.

== Implementation details ==

 * Move the page title out of pagetitles.py and into the view.
 * Added DistributionSetActionNavigationMenu for the right portlet
 * Convert DistributionSetView to inherit from LaunchpadView and make it
   implement IRegistryCollectionNavigationMenu for the other right portlet.
 * Turned the count attribute into a property and expose this on the /distros
   page

== Tests ==

== Demo and Q/A ==

Visit https://launchpad.dev/distros and observe the goodness.

= 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/pagetitles.py
  lib/lp/registry/browser/product.py
  lib/canonical/launchpad/icing/style-3-0.css
  lib/lp/registry/templates/projects-index.pt
  lib/lp/registry/templates/distros-index.pt
  lib/lp/registry/browser/person.py
  lib/lp/registry/templates/products-index.pt
  lib/lp/registry/browser/distribution.py
  lib/lp/registry/browser/project.py

== Pylint notices ==

lib/lp/registry/browser/person.py
    229: [F0401] Unable to import 'lazr.uri' (No module named uri)

Revision history for this message
Abel Deuring (adeuring) wrote :

Hi Barry,

again, a nice branch. Aside from my nitpick about the overly precise font size for .application-summary, I only noticed one "spurious change":

> === modified file 'lib/lp/registry/browser/person.py'
> --- lib/lp/registry/browser/person.py 2009-08-20 16:13:15 +0000
> +++ lib/lp/registry/browser/person.py 2009-08-20 22:57:10 +0000
> @@ -1337,6 +1337,7 @@
>
>
> class PersonSetActionNavigationMenu(RegistryCollectionActionMenuBase):
> +

please remove the empty line.

> """Action menu for `PeopleSearchView`."""
> usedfor = IPersonSet
> links = ['register_team', 'register_project', 'create_account',

Abel

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

On Aug 21, 2009, at 10:47 AM, Abel Deuring wrote:

>Review: Approve
>Hi Barry,
>
>again, a nice branch. Aside from my nitpick about the overly precise font size for .application-summary, I only noticed one "spurious change":
>
>> === modified file 'lib/lp/registry/browser/person.py'
>> --- lib/lp/registry/browser/person.py 2009-08-20 16:13:15 +0000
>> +++ lib/lp/registry/browser/person.py 2009-08-20 22:57:10 +0000
>> @@ -1337,6 +1337,7 @@
>>
>>
>> class PersonSetActionNavigationMenu(RegistryCollectionActionMenuBase):
>> +
>
>please remove the empty line.
>
>> """Action menu for `PeopleSearchView`."""
>> usedfor = IPersonSet
>> links = ['register_team', 'register_project', 'create_account',

Thanks for the review Abel. I've already noticed that when I generated the
diff and had hoped the bzr push would beat the bzr send. It didn't but I
already fixed that line :)

Thanks again for the reviews. I'm going to land both of these branches
together via ec2.

-Barry

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/launchpad/icing/style-3-0.css'
2--- lib/canonical/launchpad/icing/style-3-0.css 2009-08-20 14:01:26 +0000
3+++ lib/canonical/launchpad/icing/style-3-0.css 2009-08-20 21:13:30 +0000
4@@ -442,6 +442,15 @@
5 color: #000;
6 font-weight: normal;
7 }
8+.application-summary {
9+ font-size: 123.1%;
10+}
11+.search-results {
12+ margin-top: 1em;
13+}
14+p.search-results {
15+ font-size: 116%;
16+}
17
18
19 /* Exceptions they may be common. */
20
21=== modified file 'lib/canonical/launchpad/pagetitles.py'
22--- lib/canonical/launchpad/pagetitles.py 2009-08-20 19:54:49 +0000
23+++ lib/canonical/launchpad/pagetitles.py 2009-08-20 22:57:10 +0000
24@@ -508,8 +508,6 @@
25
26 distroseriessourcepackagerelease_index = ContextTitle('%s')
27
28-distros_index = 'Distributions registered in Launchpad'
29-
30 edit_bug_supervisor = ContextTitle('Edit bug supervisor for %s')
31
32 errorservice_config = 'Configure error log'
33@@ -1065,8 +1063,6 @@
34
35 project_translators = ContextTitle('Set translation group for %s')
36
37-projects_index = 'Project groups registered in Launchpad'
38-
39 projects_request = 'Launchpad Translations: Request a project group'
40
41 projects_search = 'Search for project groups in Launchpad'
42
43=== modified file 'lib/lp/registry/browser/configure.zcml'
44--- lib/lp/registry/browser/configure.zcml 2009-08-20 14:01:26 +0000
45+++ lib/lp/registry/browser/configure.zcml 2009-08-20 22:57:10 +0000
46@@ -456,16 +456,18 @@
47 template="../templates/project-series-portlet-details.pt"/>
48 <browser:menus
49 classes="
50- ProjectFacets
51 ProjectActionMenu
52+ ProjectAnswersMenu
53+ ProjectBountiesMenu
54+ ProjectBugsMenu
55 ProjectEditNavigationMenu
56+ ProjectFacets
57 ProjectOverviewMenu
58- ProjectBountiesMenu
59- ProjectBugsMenu
60 ProjectSeriesSpecificationsMenu
61+ ProjectSetContextMenu
62+ ProjectSetNavigationMenu
63 ProjectSpecificationsMenu
64- ProjectSetContextMenu
65- ProjectAnswersMenu"
66+ "
67 module="lp.registry.browser.project"/>
68 <browser:defaultView
69 for="lp.registry.interfaces.distributionsourcepackage.IDistributionSourcePackage"
70@@ -2010,15 +2012,17 @@
71 template="../../app/templates/generic-edit.pt"/>
72 <browser:menus
73 classes="
74+ DistributionBountiesMenu
75+ DistributionBugsMenu
76 DistributionFacets
77+ DistributionMirrorsNavigationMenu
78+ DistributionNavigationMenu
79 DistributionOverviewMenu
80- DistributionBugsMenu
81- DistributionBountiesMenu
82+ DistributionSetActionNavigationMenu
83+ DistributionSetContextMenu
84+ DistributionSetFacets
85 DistributionSpecificationsMenu
86- DistributionSetFacets
87- DistributionSetContextMenu
88- DistributionNavigationMenu
89- DistributionMirrorsNavigationMenu"
90+ "
91 module="lp.registry.browser.distribution"/>
92 <browser:defaultView
93 for="lp.registry.interfaces.sourcepackage.ISourcePackage"
94
95=== modified file 'lib/lp/registry/browser/distribution.py'
96--- lib/lp/registry/browser/distribution.py 2009-08-20 11:15:55 +0000
97+++ lib/lp/registry/browser/distribution.py 2009-08-20 22:59:38 +0000
98@@ -20,11 +20,12 @@
99 'DistributionFacets',
100 'DistributionLanguagePackAdminView',
101 'DistributionNavigation',
102+ 'DistributionPPASearchView',
103 'DistributionPackageSearchView',
104 'DistributionPendingReviewMirrorsView',
105- 'DistributionPPASearchView',
106 'DistributionSeriesMirrorsRSSView',
107 'DistributionSeriesMirrorsView',
108+ 'DistributionSetActionNavigationMenu',
109 'DistributionSetBreadcrumbBuilder',
110 'DistributionSetContextMenu',
111 'DistributionSetFacets',
112@@ -46,6 +47,8 @@
113
114 from canonical.cachedproperty import cachedproperty
115 from lp.registry.browser.announcement import HasAnnouncementsView
116+from lp.registry.browser.menu import (
117+ IRegistryCollectionNavigationMenu, RegistryCollectionActionMenuBase)
118 from lp.soyuz.browser.archive import traverse_distro_archive
119 from lp.bugs.browser.bugtask import BugTargetTraversalMixin
120 from lp.soyuz.browser.build import BuildRecordsView
121@@ -770,12 +773,21 @@
122 self.batchnav = BatchNavigator(results, self.request)
123
124
125-class DistributionSetView:
126-
127- def __init__(self, context, request):
128- self.context = context
129- self.request = request
130-
131+class DistributionSetActionNavigationMenu(RegistryCollectionActionMenuBase):
132+ """Action menu for `DistributionSetView`."""
133+
134+ usedfor = IDistributionSet
135+ links = ['register_team', 'register_project', 'create_account']
136+
137+
138+class DistributionSetView(LaunchpadView):
139+ """View for /distros top level collection."""
140+
141+ implements(IRegistryCollectionNavigationMenu)
142+
143+ page_title = 'Distributions registered in Launchpad'
144+
145+ @cachedproperty
146 def count(self):
147 return self.context.count()
148
149
150=== modified file 'lib/lp/registry/browser/person.py'
151--- lib/lp/registry/browser/person.py 2009-08-20 16:13:15 +0000
152+++ lib/lp/registry/browser/person.py 2009-08-20 22:57:10 +0000
153@@ -1337,6 +1337,7 @@
154
155
156 class PersonSetActionNavigationMenu(RegistryCollectionActionMenuBase):
157+
158 """Action menu for `PeopleSearchView`."""
159 usedfor = IPersonSet
160 links = ['register_team', 'register_project', 'create_account',
161
162=== modified file 'lib/lp/registry/browser/product.py'
163--- lib/lp/registry/browser/product.py 2009-08-20 16:13:15 +0000
164+++ lib/lp/registry/browser/product.py 2009-08-20 20:58:11 +0000
165@@ -1396,27 +1396,27 @@
166
167 max_results_to_display = config.launchpad.default_batch_size
168 results = None
169- searchrequested = False
170+ search_requested = False
171
172 def initialize(self):
173- """See `LaunchpadFormView`."""
174+ """See `LaunchpadView`."""
175 form = self.request.form_ng
176 self.search_string = form.getOne('text')
177 if self.search_string is not None:
178- self.searchrequested = True
179+ self.search_requested = True
180
181 def all_batched(self):
182 return BatchNavigator(self.context.all_active, self.request)
183
184 @cachedproperty
185 def matches(self):
186- if not self.searchrequested:
187+ if not self.search_requested:
188 return None
189 pillarset = getUtility(IPillarNameSet)
190 return pillarset.count_search_matches(self.search_string)
191
192 @cachedproperty
193- def searchresults(self):
194+ def search_results(self):
195 search_string = self.search_string.lower()
196 limit = self.max_results_to_display
197 return getUtility(IPillarNameSet).search(search_string, limit)
198
199=== modified file 'lib/lp/registry/browser/project.py'
200--- lib/lp/registry/browser/project.py 2009-08-19 20:45:31 +0000
201+++ lib/lp/registry/browser/project.py 2009-08-20 21:55:55 +0000
202@@ -6,6 +6,7 @@
203 __metaclass__ = type
204
205 __all__ = [
206+ 'ProjectActionMenu',
207 'ProjectAddProductView',
208 'ProjectAddQuestionView',
209 'ProjectAddView',
210@@ -18,14 +19,14 @@
211 'ProjectFacets',
212 'ProjectMaintainerReassignmentView',
213 'ProjectNavigation',
214+ 'ProjectOverviewMenu',
215 'ProjectRdfView',
216 'ProjectReviewView',
217- 'ProjectActionMenu',
218- 'ProjectOverviewMenu',
219 'ProjectSeriesSpecificationsMenu',
220 'ProjectSetBreadcrumbBuilder',
221 'ProjectSetContextMenu',
222 'ProjectSetNavigation',
223+ 'ProjectSetNavigationMenu',
224 'ProjectSetView',
225 'ProjectSpecificationsMenu',
226 'ProjectView',
227@@ -49,6 +50,8 @@
228 from lp.registry.interfaces.project import (
229 IProject, IProjectSeries, IProjectSet)
230 from lp.registry.browser.announcement import HasAnnouncementsView
231+from lp.registry.browser.menu import (
232+ IRegistryCollectionNavigationMenu, RegistryCollectionActionMenuBase)
233 from lp.registry.browser.product import (
234 ProductAddView, ProjectAddStepOne, ProjectAddStepTwo)
235 from canonical.launchpad.browser.branding import BrandingChangeView
236@@ -475,10 +478,18 @@
237 return ProjectGroupAddStepOne
238
239
240+class ProjectSetNavigationMenu(RegistryCollectionActionMenuBase):
241+ """Action menu for project group index."""
242+ usedfor = IProjectSet
243+ links = ['register_team', 'register_project', 'create_account']
244+
245+
246 class ProjectSetView(LaunchpadView):
247-
248- header = "Project groups registered in Launchpad"
249- page_title = header
250+ """View for project group index page."""
251+
252+ implements(IRegistryCollectionNavigationMenu)
253+
254+ page_title = "Project groups registered in Launchpad"
255
256 def __init__(self, context, request):
257 super(ProjectSetView, self).__init__(context, request)
258@@ -488,17 +499,26 @@
259 self.malone = self.form.getOne('malone', None)
260 self.bazaar = self.form.getOne('bazaar', None)
261 self.text = self.form.getOne('text', None)
262- self.searchrequested = False
263+ self.search_requested = False
264+ self.search_string = None
265 if (self.text is not None or
266 self.bazaar is not None or
267 self.malone is not None or
268 self.rosetta is not None or
269 self.soyuz is not None):
270- self.searchrequested = True
271+ self.search_requested = True
272 self.results = None
273 self.matches = 0
274
275- def searchresults(self):
276+ def initialize(self):
277+ """See `LaunchpadView`."""
278+ form = self.request.form_ng
279+ self.search_string = form.getOne('text')
280+ if self.search_string is not None:
281+ self.search_requested = True
282+
283+ @cachedproperty
284+ def search_results(self):
285 """Use searchtext to find the list of Projects that match
286 and then present those as a list. Only do this the first
287 time the method is called, otherwise return previous results.
288
289=== modified file 'lib/lp/registry/templates/distros-index.pt'
290--- lib/lp/registry/templates/distros-index.pt 2009-07-17 17:59:07 +0000
291+++ lib/lp/registry/templates/distros-index.pt 2009-08-20 22:57:10 +0000
292@@ -6,35 +6,23 @@
293 xml:lang="en"
294 lang="en"
295 dir="ltr"
296- metal:use-macro="view/macro:page/pillarindex"
297+ metal:use-macro="view/macro:page/main_side"
298 i18n:domain="launchpad"
299 >
300 <body>
301 <h1 metal:fill-slot="heading">Registered distributions</h1>
302
303-<metal:rightportlets fill-slot="portlets_two">
304- <div class="portlet">
305- <h2>About these distributions</h2>
306-
307- <div class="portletBody portletContent">
308- <img src="/@@/info" />
309- Launchpad can manage the source packages, builds, archive publishing
310- and historical record-keeping necessary to create a distribution.
311- Some of the distributions described here are fully managed in
312- Launchpad, others are managed elsewhere but Launchpad keeps track of
313- their progress.
314- </div>
315- </div>
316-</metal:rightportlets>
317-
318-<div metal:fill-slot="main">
319-
320- <div id="application-summary">
321- Launchpad can keep track of the software packages in a distribution,
322- and provide information about the bugs, translations and code
323- associated with each of them.
324- </div>
325-
326+ <tal:side metal:fill-slot="side">
327+ <tal:menu replace="structure view/@@+global-actions" />
328+ <tal:menu replace="structure context/@@+related-pages" />
329+ </tal:side>
330+
331+ <div metal:fill-slot="main">
332+ There are currently
333+ <strong class="registry-stat"
334+ tal:content="view/count">2</strong>
335+ distributions registered in Launchpad.
336+ <p/>
337 <table class="listing">
338 <thead>
339 <tr>
340@@ -67,6 +55,13 @@
341 and its information might be out of date.
342 </p>
343
344+ <div id="application-summary">
345+ Launchpad manages the source packages, builds, archive publishing, bugs,
346+ translations, code, and historical record-keeping necessary to create a
347+ distribution. Some of the distributions described here are fully
348+ managed in Launchpad, others are managed elsewhere with Launchpad
349+ keeping track of their progress.
350+ </div>
351 </div>
352
353 </body>
354
355=== modified file 'lib/lp/registry/templates/products-index.pt'
356--- lib/lp/registry/templates/products-index.pt 2009-08-20 16:13:15 +0000
357+++ lib/lp/registry/templates/products-index.pt 2009-08-20 21:13:30 +0000
358@@ -43,17 +43,17 @@
359 tal:attributes="action request/URL"
360 accept-charset="UTF-8">
361 <input size="30" name="text" tal:attributes="value view/search_string" />
362- <input tal:condition="view/searchrequested"
363+ <input tal:condition="view/search_requested"
364 type="submit"
365 value="Search again"
366 />
367- <input tal:condition="not: view/searchrequested"
368+ <input tal:condition="not: view/search_requested"
369 type="submit"
370 value="Search projects"
371 />
372 </form>
373
374- <tal:searching condition="view/searchrequested">
375+ <tal:searching condition="view/search_requested">
376
377 <p id="empty-search-string" tal:condition="not: view/search_string">
378 Enter one or more words related to the project you want to find.
379@@ -69,22 +69,22 @@
380 do another search with more relevant search terms.
381 </p>
382
383- <p tal:condition="view/searchresults">
384- <span tal:replace="view/matches">3</span>
385- <span tal:condition="python: view.matches == 1">project found</span>
386- <span tal:condition="python: view.matches != 1">projects found</span>
387- matching &#8220;<tal:string replace="view/search_string" />&#8221;
388- <span tal:condition="view/tooManyResultsFound">,
389- showing the most relevant
390- <span tal:replace="view/max_results_to_display" />
391- </span>
392- </p>
393+ <tal:searching tal:condition="view/search_results">
394+ <p class="search-results">
395+ <span tal:replace="view/matches">3</span>
396+ <span tal:condition="python: view.matches == 1">project found</span>
397+ <span tal:condition="python: view.matches != 1">projects found</span>
398+ matching &#8220;<tal:string replace="view/search_string" />&#8221;
399+ <span tal:condition="view/tooManyResultsFound">,
400+ showing the most relevant
401+ <span tal:replace="view/max_results_to_display" />
402+ </span>
403+ </p>
404+ </tal:searching>
405
406- <table
407- tal:condition="view/matches"
408- tal:define="results view/searchresults"
409- id="search-results"
410- >
411+ <table tal:condition="view/matches"
412+ tal:define="results view/search_results"
413+ class="search-results">
414 <tbody>
415 <tr tal:repeat="pillarname results"
416 tal:replace="structure pillarname/@@+listing-simple">
417@@ -94,42 +94,32 @@
418
419 <div id="application-summary">
420 <p class="error"
421- tal:condition="not: view/searchresults">
422+ tal:condition="not: view/search_results">
423 No projects matching
424 &#8220;<span tal:replace="view/search_string">moz</span>&#8221;
425 were found.
426 </p>
427
428- <h2>Haven&#8217;t found your project?</h2>
429-
430 <p>
431- If the project you are looking for isn't here, go ahead and register
432- it yourself!
433+ If the project you are looking for isn't here, go ahead and
434+ <a href=""
435+ tal:attributes="href view/menu:overview/register_project/url"
436+ >register it yourself!</a>
437 </p>
438
439- <ul class="rollover buttons">
440- <li>
441- <a href="/projects/+new">
442- <img
443- alt="Register a project"
444- src="/+icing/but-lrg-registerproj.gif"
445- />
446- </a>
447- </li>
448- </ul>
449 </div>
450 </tal:non-empty-search-string>
451
452 </tal:searching>
453- <tal:no_search condition="not: view/searchrequested">
454+ <tal:no_search condition="not: view/search_requested">
455
456- <p id="application-summary">
457+ <p class="application-summary">
458 Launchpad helps people to work together by making it easy to share
459 code, bug reports, translations, and ideas. To get started, search here
460 for a project you want to know more about.
461 </p>
462
463- <p>
464+ <p class="application-summary">
465 If you find a project you run, <a href="/launchpad/+addquestion">ask
466 us</a> and we'll hand the project over to you.
467 <br />To set that project up for translation to your language, <a
468
469=== modified file 'lib/lp/registry/templates/projects-index.pt'
470--- lib/lp/registry/templates/projects-index.pt 2009-07-17 17:59:07 +0000
471+++ lib/lp/registry/templates/projects-index.pt 2009-08-20 21:13:30 +0000
472@@ -6,96 +6,81 @@
473 xml:lang="en"
474 lang="en"
475 dir="ltr"
476- metal:use-macro="view/macro:page/pillarindex"
477+ metal:use-macro="view/macro:page/main_side"
478 i18n:domain="launchpad"
479 >
480 <body>
481- <h1 metal:fill-slot="heading">Project groups in Launchpad</h1>
482-
483-<metal:rightportlets fill-slot="portlets_two">
484- <div class="portlet">
485- <h2>About Projects in Launchpad:</h2>
486- <div class="portletBody portletContent">
487- <img src="/@@/info" />
488- There are currently
489- <a href="+all"><span tal:replace="context/count_all">5</span>
490- projects</a> registered.
491- <br />
492- <br />
493- Only the Launchpad administrators and registry experts can register new
494- project groups. You can register an individual application (what we call
495- a project). To do so, first <a href="/projects/">search to see if your
496- project is already in Launchpad</a>.
497+ <h1 metal:fill-slot="heading">Project groups</h1>
498+
499+ <tal:side metal:fill-slot="side">
500+ <tal:menu replace="structure view/@@+global-actions" />
501+ <tal:menu replace="structure context/@@+related-pages" />
502+ </tal:side>
503+
504+ <div metal:fill-slot="main">
505+ <p>There are
506+ <strong class="registry-stat"
507+ tal:content="context/count_all">5</strong>
508+ project groups registered in Launchpad.
509+ </p>
510+
511+ <form class="central" method="get" accept-charset="UTF-8">
512+ <input size="30" name="text" tal:attributes="value view/search_string" />
513+ <input
514+ tal:condition="view/search_requested"
515+ type="submit" value="Search Again" />
516+ <input
517+ tal:condition="not: view/search_requested"
518+ type="submit" value="Search project groups" />
519+ </form>
520+
521+ <div tal:condition="view/search_requested">
522+ <tal:searching tal:condition="view/search_results">
523+ <p class="search-results">
524+ <span tal:replace="view/matches">3</span>
525+ <span tal:condition="python: view.matches == 1">project
526+ group found</span>
527+ <span tal:condition="python: view.matches != 1">projects
528+ groups found</span>
529+ matching
530+ &#8220;<tal:string replace="view/search_string" />&#8221;
531+ </p>
532+ </tal:searching>
533+
534+ <table tal:condition="view/matches"
535+ tal:define="results view/search_results"
536+ class="search-results">
537+ <tbody>
538+ <tr tal:repeat="product results">
539+ <td tal:content="structure product/@@+listing-detailed" />
540+ </tr>
541+ </tbody>
542+ </table>
543+
544+ <p class="error application-summary"
545+ tal:condition="not: view/search_results">
546+ No project groups matching
547+ &#8220;<span tal:replace="view/search_string">moz</span>&#8221;
548+ were found.
549+ </p>
550+ </div>
551+ <p class="application-summary">
552+ In Launchpad, <dfn>project groups</dfn> are special groups of
553+ software projects that share a development community. Examples
554+ include <a href="gnome">Gnome</a>,
555+ <a href="kde"><abbr>KDE</abbr></a>, and
556+ <a href="mozilla">Mozilla</a>. Launchpad allows you to group
557+ several projects together into a project group, to make coordination
558+ and tracking simpler.
559+ </p>
560+
561+ <p class="application-summary">
562+ Only the Launchpad administrators and registry experts can register
563+ new project groups. You can register an individual application (what
564+ we call a project). To do so, first <a href="/projects/">search to
565+ see if your project is already in Launchpad</a>.
566+ </p>
567+
568 </div>
569- </div>
570-</metal:rightportlets>
571-
572-<div metal:fill-slot="main">
573-
574- <p>
575- In Launchpad, <dfn>project groups</dfn> are special groups of software
576- projects that share a development community.
577- Examples include <a href="gnome">Gnome</a>,
578- <a href="kde"><abbr>KDE</abbr></a>, and
579- <a href="mozilla">Mozilla</a>. Launchpad allows you to group several
580- projects together in a project, to make coordination and tracking
581- simpler.
582- </p>
583-
584- <form method="get" accept-charset="UTF-8">
585- <label for="text">Search projects:</label>
586- <input name="text" tal:attributes="value view/text" />
587- <input
588- tal:condition="view/searchrequested"
589- type="submit" value="Search Again" />
590- <input
591- tal:condition="not: view/searchrequested"
592- type="submit" value="Search" />
593- </form>
594-
595- <tal:notsearching condition="not:view/searchrequested">
596- <p>
597- For individual computer programs, applications or libraries,
598- see <a href="/projects">projects</a>.
599- </p>
600-
601- <p>
602- For groups of people, see
603- <a href="/people">Launchpad teams</a>.
604- </p>
605- </tal:notsearching>
606-
607- <tal:searching condition="view/searchrequested">
608-
609- <table class="data" tal:define="results view/searchresults">
610- <thead>
611- <tr class="results">
612- <th colspan="0">
613- <span tal:replace="view/matches">3</span>
614- <span tal:condition="python: view.matches == 1">result</span>
615- <span tal:condition="python: view.matches != 1">results</span>
616- </th>
617- </tr>
618- </thead>
619- <tbody>
620- <tr tal:condition="view/matches"
621- tal:repeat="product results">
622- <td tal:content="structure product/@@+listing-detailed" />
623- </tr>
624- <tr tal:condition="not: view/matches">
625- <td tal:condition="not: view/matches">
626- <p>No projects matching
627- &#8220;<span tal:replace="view/text">moz</span>&#8221;
628- were found.
629- You can <a href="+new">register a new project</a>.</p>
630- </td>
631- </tr>
632- </tbody>
633- </table>
634-
635- </tal:searching>
636-
637-</div>
638-
639-</body>
640+ </body>
641 </html>