Merge lp:~jcsackett/launchpad/unknown-blueprints-service-597738 into lp:launchpad

Proposed by j.c.sackett
Status: Merged
Approved by: Curtis Hovey
Approved revision: no longer in the source branch.
Merged at revision: 11571
Proposed branch: lp:~jcsackett/launchpad/unknown-blueprints-service-597738
Merge into: lp:launchpad
Prerequisite: lp:~jcsackett/launchpad/deprecate-remaining-official-bools
Diff against target: 914 lines (+464/-59)
13 files modified
lib/lp/blueprints/browser/configure.zcml (+4/-8)
lib/lp/blueprints/browser/specificationtarget.py (+82/-7)
lib/lp/blueprints/browser/tests/test_specificationtarget.py (+127/-3)
lib/lp/blueprints/stories/blueprints/xx-creation.txt (+71/-29)
lib/lp/blueprints/stories/blueprints/xx-productseries.txt (+12/-0)
lib/lp/blueprints/stories/standalone/xx-batching.txt (+19/-5)
lib/lp/blueprints/stories/standalone/xx-index.txt (+14/-0)
lib/lp/blueprints/stories/standalone/xx-overview.txt (+11/-0)
lib/lp/blueprints/stories/standalone/xx-views.txt (+21/-4)
lib/lp/blueprints/templates/unknown-specs.pt (+93/-0)
lib/lp/registry/browser/distribution.py (+7/-0)
lib/lp/registry/browser/product.py (+1/-1)
lib/lp/registry/browser/tests/pillar-views.txt (+2/-2)
To merge this branch: bzr merge lp:~jcsackett/launchpad/unknown-blueprints-service-597738
Reviewer Review Type Date Requested Status
Curtis Hovey (community) ui Approve
Guilherme Salgado (community) ui* Approve
Brad Crittenden (community) code Approve
Registry Administrators ui Pending
Review via email: mp+35018@code.launchpad.net

Commit message

Updates the blueprints service page to use the blueprints_usage enum and display relevant data for each setting.

Description of the change

= Summary =

Updates the blueprints service to present relevant data for the UNKNOWN, EXTERNAL, and NOT_APPLICABLE settings of the blueprints_usage enum.

== Proposed fix ==

Modify the template and/or view to detect the usage status and present the correct template/info.

== Pre-implementation notes ==

Spoke with Curtis Hovey (sinzui) and Brad Crittenden (bac).

== Implementation details ==

The HasSpecificationsView has been updated to select a different template for contexts of Product, Distribution, and DistributionSeries in the event that the blueprints_usage enum indicates the context doesn't use Launchpad.

The new template presents slightly different information based on the enum case, and provides some basic controls if relevant (e.g. configure blueprints if the user has edit permissions).

== Tests ==

bin/test -vvcm lp.blueprints.browser

== Demo and Q/A ==

In Launchpad.dev, signed in as admin, go to http://blueprints.launchpad.dev/thunderbird. Try each configuration option and confirm that the presentation makes sense.

Linting changed files:
  lib/lp/answers/browser/questiontarget.py
  lib/lp/answers/doc/question.txt
  lib/lp/answers/doc/questionsets.txt
  lib/lp/app/enums.py
  lib/lp/blueprints/browser/configure.zcml
  lib/lp/blueprints/browser/specificationtarget.py
  lib/lp/blueprints/browser/tests/test_specificationtarget.py
  lib/lp/blueprints/templates/unknown-specs.pt
  lib/lp/registry/browser/distribution.py
  lib/lp/registry/browser/pillar.py
  lib/lp/registry/browser/productseries.py
  lib/lp/registry/browser/tests/distribution-views.txt
  lib/lp/registry/browser/tests/pillar-views.txt
  lib/lp/registry/browser/tests/product-views.txt
  lib/lp/registry/browser/tests/productseries-views.txt
  lib/lp/registry/browser/tests/projectgroup-views.txt
  lib/lp/registry/doc/distribution.txt
  lib/lp/registry/doc/product.txt
  lib/lp/registry/templates/distribution-index.pt
  lib/lp/registry/templates/distribution-search.pt
  lib/lp/registry/templates/distroseries-index.pt
  lib/lp/registry/templates/product-index.pt
  lib/lp/registry/templates/productseries-index.pt
  lib/lp/registry/templates/project-index.pt

Lint output corrupted by merged dependent branch.

To post a comment you must log in.
Revision history for this message
j.c.sackett (jcsackett) wrote :
Download full text (11.9 KiB)

Sorry about the bad update. Just in case of divergences between the prereq and this post update from devel, here's the diff:

=== modified file 'lib/lp/blueprints/browser/configure.zcml'
--- lib/lp/blueprints/browser/configure.zcml 2010-07-27 17:17:59 +0000
+++ lib/lp/blueprints/browser/configure.zcml 2010-09-08 20:11:11 +0000
@@ -53,8 +53,7 @@
             name="+mdz-specs.csv"
             attribute="mdzCsv"/>
         <browser:page
- name="+specs"
- template="../templates/hasspecifications-specs.pt"/>
+ name="+specs"/>
         <browser:page
             name="+portlet-latestspecs"
             template="../templates/specificationtarget-portlet-latestspecs.pt"/>
@@ -500,8 +499,7 @@
             class="lp.blueprints.browser.specificationtarget.HasSpecificationsView"
             permission="zope.Public">
             <browser:page
- name="+specs"
- template="../templates/hasspecifications-specs.pt"/>
+ name="+specs"/>
         </browser:pages>
         <browser:page
             for="lp.blueprints.interfaces.specificationtarget.ISpecificationGoal"
@@ -532,8 +530,7 @@
             class="lp.blueprints.browser.specificationtarget.HasSpecificationsView"
             permission="zope.Public">
             <browser:page
- name="+specs"
- template="../templates/hasspecifications-specs.pt"/>
+ name="+specs"/>
             <browser:page
                 name="+portlet-latestspecs"
                 template="../templates/specificationtarget-portlet-latestspecs.pt"/>
@@ -583,8 +580,7 @@
         facet="specifications"
         permission="zope.Public">
         <browser:page
- name="+specs"
- template="../templates/hasspecifications-specs.pt"/>
+ name="+specs"/>
         <browser:page
             name="+portlet-latestspecs"
             template="../templates/specificationtarget-portlet-latestspecs.pt"/>

=== modified file 'lib/lp/blueprints/browser/specificationtarget.py'
--- lib/lp/blueprints/browser/specificationtarget.py 2010-08-24 10:45:57 +0000
+++ lib/lp/blueprints/browser/specificationtarget.py 2010-09-09 18:06:03 +0000
@@ -15,6 +15,7 @@

 from operator import itemgetter

+from z3c.ptcompat import ViewPageTemplateFile
 from zope.component import queryMultiAdapter

 from canonical.config import config
@@ -32,6 +33,8 @@
     Link,
     )
 from canonical.lazr.utils import smartquote
+from lp.app.enums import service_uses_launchpad
+from lp.app.interfaces.launchpad import IServiceUsage
 from lp.blueprints.interfaces.specification import (
     SpecificationFilter,
     SpecificationSort,
@@ -133,6 +136,50 @@
     is_sprint = False
     has_drivers = False

+ # Templates for the various conditions of blueprints:
+ # * On Launchpad
+ # * External
+ # * Disabled
+ # * Unknown
+ default_template = ViewPageTemplateFile(
+ '../templates/hasspecifications-specs.pt')
+ not_launchpad_template = ViewPageTemplateFile(
+ '../templates/unknown-specs.pt')
+
+ @property
+ def template(self):
+ # If the template has been defined in the zcml, use that, as t...

Revision history for this message
Brad Crittenden (bac) wrote :
Download full text (10.3 KiB)

Hi Jon,

Thanks for working on this branch. I was unaware of the added
complexity of the whole template/page/portlet mess.

I like the work you've done here modulo the on-going discussion with
Gary about a better approach to the template swapping. I'm marking
the MP 'needs information' so I can track the change you come up with,
but the branch is very close.

You mentioned lint being confused by the dependency on another
branch. Even so, please ensure these are not for real:

./lib/lp/blueprints/browser/specificationtarget.py
     251: E301 expected 1 blank line, found 0
     254: E301 expected 1 blank line, found 0
./lib/lp/blueprints/templates/unknown-specs.pt
      38: Line has trailing whitespace.
./lib/lp/registry/doc/distribution.txt
     582: source exceeds 78 characters.
     584: source exceeds 78 characters.

> === modified file 'lib/lp/blueprints/browser/specificationtarget.py'
> --- lib/lp/blueprints/browser/specificationtarget.py 2010-08-24 10:45:57 +0000
> +++ lib/lp/blueprints/browser/specificationtarget.py 2010-09-10 14:48:00 +0000
> @@ -15,6 +15,7 @@
>
> from operator import itemgetter
>
> +from z3c.ptcompat import ViewPageTemplateFile
> from zope.component import queryMultiAdapter
>
> from canonical.config import config
> @@ -32,6 +33,8 @@
> Link,
> )
> from canonical.lazr.utils import smartquote
> +from lp.app.enums import service_uses_launchpad
> +from lp.app.interfaces.launchpad import IServiceUsage
> from lp.blueprints.interfaces.specification import (
> SpecificationFilter,
> SpecificationSort,
> @@ -133,6 +136,50 @@
> is_sprint = False
> has_drivers = False
>
> + # Templates for the various conditions of blueprints:
> + # * On Launchpad
> + # * External
> + # * Disabled
> + # * Unknown
> + default_template = ViewPageTemplateFile(
> + '../templates/hasspecifications-specs.pt')
> + not_launchpad_template = ViewPageTemplateFile(
> + '../templates/unknown-specs.pt')
> +
> + @property
> + def template(self):
> + # If the template has been defined in the zcml, use that, as this
> + # isn't the base service page for blueprints.
> + if hasattr(self, 'index'):

As we discussed on IRC, you should use 'safe_hasattr' from
canonical.lazr.utils. Also, as I suggested please check around and
see if there is a more obvious technique for this. Sadly I cannot
offer any other solution.

> + return super(HasSpecificationsView, self).template
> +
> + # Sprints and Persons don't have a usage enum for blueprints, so we
> + # have to fallback to the default.
> + if (ISprint.providedBy(self.context)
> + or IPerson.providedBy(self.context)):
> + return self.default_template
> +
> + # ProjectGroups are a special case, as their products may be a
> + # combination of usage settings. Use the default, since it handles
> + # fetching anything that's set for ProjectGroups, and it's not correct
> + # to say anything about the ProjectGroup's usage.
> + if IProjectGroup.providedBy(self.context):
> + return self.default_template
> +
> + # If specific...

review: Needs Information (code)
Revision history for this message
Curtis Hovey (sinzui) wrote :

On Fri, 2010-09-10 at 15:59 +0000, Brad Crittenden wrote:
> > + @property
> > + def template(self):
> > + # If the template has been defined in the zcml, use that,
> as this
> > + # isn't the base service page for blueprints.
> > + if hasattr(self, 'index'):
>
> As we discussed on IRC, you should use 'safe_hasattr' from
> canonical.lazr.utils. Also, as I suggested please check around and
> see if there is a more obvious technique for this. Sadly I cannot
> offer any other solution.

We can abandon safe_hasattr when lpnet is on lucid. This issue was fixed
several years ago in pythong 2.6

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

Good changes Jon. Please alphabetize the imports where you added safe_hasattr.

review: Approve (code)
Revision history for this message
Guilherme Salgado (salgado) wrote :

The UI changes look good to me, although it's confusing that the new template (lib/lp/blueprints/templates/unknown-specs.pt) uses the term "specification[s]" when the rest of the page uses "blueprint[s]". I think we should use only one of them, and most likely the preferred would be "blueprint[s]" as that's what is used in the top heading.

review: Needs Fixing (ui*)
Revision history for this message
Curtis Hovey (sinzui) wrote :

Gentleman, I think the reviewers and developers need to consider why we are making this change, and what we are really changing.

Our goal is to communicate to the user where certain kinds of activities are performed--which site does the user need to visit to complete his goal. Launchpad blueprints is a branded service that provides specifications and documentation. The page needs to be talking about "specifications and documentation" because other sites, most often wikis, do not use the term blueprints. We want to allow the users to provide a URL when they choose external (maybe repurpose the wiki field). The page is not really stating that Launchpad Blueprints allows the user to track specifications and documentation...I see no reason why I would turn the feature on since it does not have a sentence stating the basics of what it is for. Specification itself may be too technical. This app is essentially about planning and documenting features.

My other concern is about what has specifications--a SpecificationTarget. I reviewed the bugs branch a few days ago and saw only IProduct was supports well. A BugTarget may be a product, projectgroup, distribution, and distributionsourcepackage. I think these are also SpediciationTargets, so everyone needs to visit an example of each to ensure the message is correct and the actions that can be takes are presented. eg, a project group's use of an app is implicit in the settings of its products. I took a brief look at this branch in launchpad.dev and am pretty sure thunderbird is somewhat right, kubuntu, firefox and mozilla are wrong.

jcsackett, Salgado, take a look at the branch again from the perspective of kinds of users who are interested in feature planning.

review: Needs Fixing
Revision history for this message
j.c.sackett (jcsackett) wrote :

I just discovered I missed comments within the diff; sorry for not replying on Friday.

> If the enum is wrong, how will the user get here? The tab will be
> disabled, so we assume they have a direct URL?

I'm not sure the tab will be disabled--if no blueprints existed, and the enum said "EXTERNAL" or something, you could still get to this view so the "${Pillar} does not use blueprints" could be displayed. In this instance if it's set to EXTERNAL but people have been using the blueprints, we continue to display the blueprints information when you get here.

The involvement menu link is disabled, but the tab along the top of the page is still there, and of course nothing stops someone from going to blueprints.launchpad.net/$PILLAR.

Now, I'm open to debate on whether we should just turn off the feature regardless of whether or not blueprints are already being used--I erred on the side of not allowing someone to just delete a bunch of work, but that might not be the decision we want to make here.

>> self.verify_involvment(context)
>
> You didn't introduce it, but please fix the typo of s/verify_involment/verify_involvement/

Uhm, not sure what you mean--looks like it already is verify_involvement?

>> def test_adaptable_to_specificationtarget(self):
>> @@ -87,6 +92,63 @@
>> '<div id="involvement" class="portlet involvement">' in view())
>>
>>
>> +class TestHasSpecificationsTemplates(TestCaseWithFactory):
>> + """Tests the selection of templates based on blueprints usage."""
>> +
>> + layer = DatabaseFunctionalLayer
>> +
>> + def setUp(self):
>> + super(TestHasSpecificationsTemplates, self).setUp()
>> + self.user = self.factory.makePerson()
>> + self.product = self.factory.makeProduct()
>> + self.naked_product = removeSecurityProxy(self.product)
>> + login_person(self.user)
>
> While I'm not a prude, I must ask why all of the nakedness here?
> Couldn't you just make self.user be the owner of the product and
> proceed fully clothed?

I wanted to test what a non-owner user has happen here; that may be the same thing as self.user, but it may not--I've been bitten by using owners/admins in the past.

> Why use 'specifications' just the once? The page says 'blueprints'
> six times and it is unclear that specifications and blueprints are the
> same from the context.
>
> Should we be more frank:
>
> <project> tracks specifications somewhere besides Launchpad, but we
> don't know where. You should check with the project maintainers for
> the location.
>
> I'm just throwing that out for thought. I'm not sure I even like the idea.

I'm playing with this right now related to the UI review; neither blueprints nor specifications are totally right for the copy, per Curtis's comments.

But consider your comments thrown into the mix.

> Thanks for the additional drive-by fixes.

I do what I can. :-P

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

On Mon, 2010-09-13 at 19:54 +0000, j.c.sackett wrote:
>
> The involvement menu link is disabled, but the tab along the top of
> the page is still there, and of course nothing stops someone from
> going to blueprints.launchpad.net/$PILLAR.
>
> Now, I'm open to debate on whether we should just turn off the feature
> regardless of whether or not blueprints are already being used--I
> erred on the side of not allowing someone to just delete a bunch of
> work, but that might not be the decision we want to make here.

The complaint from project owners is that users created blueprints on
Launchpad instead of the upstream wiki. The project owner can enable
blueprints if he chooses to. Blueprints should be off until enabled.

I went to firefox in lp.dev and saw blueprints on. As the owner of the
project I decided to turn blueprints off, so I choses the configure
link, and was surprised that Blueprints was off. More over, Saving the
state of unknown did not disable blueprints. The state of the app does
not work like answers. Remember, we are working on other applications to
ensure all apps have the same kind of behaviours.

--
__Curtis C. Hovey_________
http://launchpad.net/

Revision history for this message
j.c.sackett (jcsackett) wrote :

> The complaint from project owners is that users created blueprints on
> Launchpad instead of the upstream wiki. The project owner can enable
> blueprints if he chooses to. Blueprints should be off until enabled.
>
> I went to firefox in lp.dev and saw blueprints on. As the owner of the
> project I decided to turn blueprints off, so I choses the configure
> link, and was surprised that Blueprints was off. More over, Saving the
> state of unknown did not disable blueprints. The state of the app does
> not work like answers. Remember, we are working on other applications to
> ensure all apps have the same kind of behaviours.

I can buy this argument, totally. I've removed the conditional that prioritizes the existence of blueprints over the status of the enum.

Revision history for this message
j.c.sackett (jcsackett) wrote :

> Our goal is to communicate to the user where certain kinds of activities are
> performed--which site does the user need to visit to complete his goal.
> Launchpad blueprints is a branded service that provides specifications and
> documentation. The page needs to be talking about "specifications and
> documentation" because other sites, most often wikis, do not use the term
> blueprints.

Given this, I've updated the template to refer to the notion of documentation, and feature planning. In the instance where blueprints is set active, I've left the template alone, as the process of setting it up tells the user that Blueprints is a branded way that Launchpad does feature planning and documentation.

> We want to allow the users to provide a URL when they choose
> external (maybe repurpose the wiki field).

Based on our prior conversation I thought we were going to simply default to "There is a wiki for this project, which may be used for this purpose," with a link to the wiki if it exists, rather than introducing an extra db field to track an external specification tracking url.

I do think it may be worthwhile to circle back and add that field and get that information (for blueprints, answers, and translations) so we can provide a specific external place, but I think those changes are out of scope for this branch.

> The page is not really stating that
> Launchpad Blueprints allows the user to track specifications and
> documentation...I see no reason why I would turn the feature on since it does
> not have a sentence stating the basics of what it is for.

Agreed; I've updated what the page says to address this.

An incremental diff with these changes is attached.

I'll be addressing your other concerns re: specificationtarget next.

1=== modified file 'lib/lp/blueprints/browser/specificationtarget.py'
2--- lib/lp/blueprints/browser/specificationtarget.py 2010-09-13 20:21:10 +0000
3+++ lib/lp/blueprints/browser/specificationtarget.py 2010-09-14 15:49:09 +0000
4@@ -26,6 +26,7 @@
5 canonical_url,
6 LaunchpadView,
7 )
8+from canonical.launchpad.webapp.authorization import check_permission
9 from canonical.launchpad.webapp.batching import BatchNavigator
10 from canonical.launchpad.webapp.breadcrumb import Breadcrumb
11 from canonical.launchpad.webapp.menu import (
12@@ -219,6 +220,15 @@
13 size=config.launchpad.default_batch_size)
14
15 @property
16+ def can_configure_blueprints(self):
17+ """Can the user configure blueprints for the `ISpecificationTarget`."""
18+ target = self.context
19+ if IProduct.providedBy(target) or IDistribution.providedBy(target):
20+ return check_permission('launchpad.Edit', self.context)
21+ else:
22+ return False
23+
24+ @property
25 def label(self):
26 mapping = {'name': self.context.displayname}
27 if self.is_person:
28
29=== modified file 'lib/lp/blueprints/browser/tests/test_specificationtarget.py'
30--- lib/lp/blueprints/browser/tests/test_specificationtarget.py 2010-09-09 18:11:41 +0000
31+++ lib/lp/blueprints/browser/tests/test_specificationtarget.py 2010-09-14 15:47:58 +0000
32@@ -18,7 +18,10 @@
33 login_person,
34 TestCaseWithFactory,
35 )
36-from lp.testing.views import create_view
37+from lp.testing.views import (
38+ create_view,
39+ create_initialized_view,
40+ )
41
42
43 class TestRegisterABlueprintButtonView(TestCaseWithFactory):
44@@ -149,6 +152,38 @@
45 view.template.filename)
46
47
48+class TestHasSpecificationsConfiguration(TestCaseWithFactory):
49+
50+ layer = DatabaseFunctionalLayer
51+
52+ def test_cannot_configure_blueprints_product_no_edit_permission(self):
53+ product = self.factory.makeProduct()
54+ view = create_initialized_view(product, '+specs')
55+ self.assertEqual(False, view.can_configure_blueprints)
56+
57+ def test_can_configure_blueprints_product_with_edit_permission(self):
58+ product = self.factory.makeProduct()
59+ login_person(product.owner)
60+ view = create_initialized_view(product, '+specs')
61+ self.assertEqual(True, view.can_configure_blueprints)
62+
63+ def test_cannot_configure_blueprints_distribution_no_edit_permission(self):
64+ distribution = self.factory.makeDistribution()
65+ view = create_initialized_view(distribution, '+specs')
66+ self.assertEqual(False, view.can_configure_blueprints)
67+
68+ def test_can_configure_blueprints_distribution_with_edit_permission(self):
69+ distribution = self.factory.makeDistribution()
70+ login_person(distribution.owner)
71+ view = create_initialized_view(distribution, '+specs')
72+ self.assertEqual(True, view.can_configure_blueprints)
73+
74+ def test_cannot_configure_blueprints_projectgroup_with_edit_permission(self):
75+ project_group = self.factory.makeProject()
76+ login_person(project_group.owner)
77+ view = create_initialized_view(project_group, '+specs')
78+ self.assertEqual(False, view.can_configure_blueprints)
79+
80 def test_suite():
81 suite = unittest.TestSuite()
82 suite.addTest(unittest.TestLoader().loadTestsFromName(__name__))
83
84=== modified file 'lib/lp/blueprints/templates/unknown-specs.pt'
85--- lib/lp/blueprints/templates/unknown-specs.pt 2010-09-10 16:34:26 +0000
86+++ lib/lp/blueprints/templates/unknown-specs.pt 2010-09-14 16:02:38 +0000
87@@ -11,49 +11,47 @@
88
89 <div metal:fill-slot="main"
90 tal:define="specs view/specs;
91- has_any_specs view/has_any_specifications">
92-
93- <div tal:condition="view/context/blueprints_usage/enumvalue:EXTERNAL">
94- <strong><tal:project replace="view/context/displayname" /> does not use Launchpad
95- for specification tracking.</strong>
96- </div>
97-
98- <div tal:condition="view/context/blueprints_usage/enumvalue:NOT_APPLICABLE">
99- <strong><tal:project replace="view/context/displayname" /> does not track
100- specifications.</strong>
101- </div>
102-
103- <div tal:condition="view/context/blueprints_usage/enumvalue:UNKNOWN">
104- <strong>
105- Launchpad does not know how
106- <tal:project replace="view/context/displayname" /> uses specifications.
107- </strong>
108- </div>
109-
110- <div tal:condition="view/context/wikiurl">
111- <p><tal:project replace="view/context/displayname" /> has a wiki, which
112- may be used for specifications.</p>
113-
114- <p>
115+ has_any_specs view/has_any_specifications;
116+ blueprints_usage view/context/blueprints_usage">
117+ <div class="top-portlet">
118+ <div id="specs-unknown">
119+ <strong>
120+ <p tal:condition="blueprints_usage/enumvalue:EXTERNAL">
121+ <tal:project replace="view/context/displayname" /> does not use Launchpad
122+ for planning or documentation.
123+ </p>
124+ <p tal:condition="blueprints_usage/enumvalue:NOT_APPLICABLE">
125+ <tal:project replace="view/context/displayname" /> does not track
126+ use feature planning or documentation.
127+ </p>
128+ <p tal:condition="blueprints_usage/enumvalue:UNKNOWN">
129+ Launchpad does not know how
130+ <tal:project replace="view/context/displayname" /> tracks feature
131+ planning or documentation.
132+ </p>
133+ </strong>
134+
135+ <p id="wiki-fallback"
136+ tal:define="wiki view/context/wikiurl"
137+ tal:condition="wiki">
138+ <tal:project replace="view/context/displayname" /> has a wiki, which
139+ may be used for feature plannning and documentation.<br />
140 <a tal:attributes="href view/context/wikiurl">
141- <tal:project replace="view/context/displayname" /> wiki
142+ <tal:project replace="view/context/displayname" /> wiki
143 </a>
144 </p>
145-
146 </div>
147- <ul class="horizontal">
148- <li>
149- <a class="info sprite"
150- href="https://help.launchpad.net/BlueprintDocumentation">
151- Read more about tracking blueprints
152- </a>
153- </li>
154- </ul>
155- <ul class="horiztonal">
156- <li>
157- <a tal:replace="structure context/menu:overview/configure_blueprints/fmt:link" />
158- </li>
159- </ul>
160+
161+ <p id="configure-support"
162+ tal:condition="view/can_configure_blueprints">
163+ Launchpad Blueprints allow your project to track feature planning and
164+ documentation.<br />
165+ <a class="info sprite"
166+ href="https://help.launchpad.net/BlueprintDocumentation">
167+ Read more about tracking feature planning with blueprints</a><br />
168+ <a tal:replace="structure context/menu:overview/configure_blueprints/fmt:link" /><br />
169+ </p>
170+ </div>
171 </div>
172 </body>
173 </html>
Revision history for this message
j.c.sackett (jcsackett) wrote :

> My other concern is about what has specifications--a SpecificationTarget. I
> reviewed the bugs branch a few days ago and saw only IProduct was supports
> well.

Valid; I've done some digging and have some explanations below.

> A BugTarget may be a product, projectgroup, distribution, and
> distributionsourcepackage. I think these are also SpediciationTargets, so
> everyone needs to visit an example of each to ensure the message is correct
> and the actions that can be takes are presented. eg, a project group's use of
> an app is implicit in the settings of its products.

So, I think products are now handled right.

Project groups are tricky--right now I have it defaulting to the old style, so that any blueprints made for any project will be shown. Given how the earlier question of whether or not to continue showing blueprints if has_any_specifications was True, I think it is appropriate to only turn this on if at least one product has LAUNCHPAD as its usage setting.

Distributions were mostly handled right; the issue with say, Kubuntu was the configure_blueprints link, which I fixed.

DSPs aren't valid for blueprints; it's always NOT_APPLICABLE and the links are all disabled. You can't even get to it by going to blueprints.launchpad.net for the DSP in question; it takes you to the overview. (see https://blueprints.edge.launchpad.net/ubuntu/+source/firefox or https://blueprints.launchpad.dev/ubuntu/+source/mozilla-firefox)

Attached is a diff of this round of changes.

1=== modified file 'lib/lp/blueprints/browser/specificationtarget.py'
2--- lib/lp/blueprints/browser/specificationtarget.py 2010-09-14 15:50:16 +0000
3+++ lib/lp/blueprints/browser/specificationtarget.py 2010-09-14 19:02:37 +0000
4@@ -138,6 +138,7 @@
5 is_project = False
6 is_series = False
7 is_sprint = False
8+ has_wiki = False
9 has_drivers = False
10
11 # Templates for the various conditions of blueprints:
12@@ -153,9 +154,9 @@
13 @property
14 def template(self):
15 # Check for the magical "index" added by the browser:page template
16- # machinery. If it exists this is actually the
17+ # machinery. If it exists this is actually the
18 # zope.app.pagetemplate.simpleviewclass.simple class that is magically
19- # mixed in by the browser:page zcml directive the template defined in
20+ # mixed in by the browser:page zcml directive the template defined in
21 # the directive should be used.
22 if safe_hasattr(self, 'index'):
23 return super(HasSpecificationsView, self).template
24@@ -167,11 +168,15 @@
25 return self.default_template
26
27 # ProjectGroups are a special case, as their products may be a
28- # combination of usage settings. Use the default, since it handles
29- # fetching anything that's set for ProjectGroups, and it's not correct
30- # to say anything about the ProjectGroup's usage.
31+ # combination of usage settings. To deal with this, check all
32+ # products, and if a product has LAUNCHPAD for the enum, the
33+ # group does; if not, assume external.
34 if IProjectGroup.providedBy(self.context):
35- return self.default_template
36+ for product in self.context.products:
37+ if service_uses_launchpad(product.blueprints_usage):
38+ return self.default_template
39+ else:
40+ return self.not_launchpad_template
41
42 # Otherwise, determine usage and provide the correct template.
43 service_usage = IServiceUsage(self.context)
44@@ -188,14 +193,19 @@
45 def initialize(self):
46 if IPerson.providedBy(self.context):
47 self.is_person = True
48- elif (IDistribution.providedBy(self.context) or
49- IProduct.providedBy(self.context)):
50- self.is_target = True
51- self.is_pillar = True
52+ elif IDistribution.providedBy(self.context):
53+ self.is_target = True
54+ self.is_pillar = True
55+ self.show_series = True
56+ elif IProduct.providedBy(self.context):
57+ self.is_target = True
58+ self.is_pillar = True
59+ self.has_wiki = True
60 self.show_series = True
61 elif IProjectGroup.providedBy(self.context):
62 self.is_project = True
63 self.is_pillar = True
64+ self.has_wiki = True
65 self.show_target = True
66 self.show_series = True
67 elif IProjectGroupSeries.providedBy(self.context):
68@@ -221,7 +231,8 @@
69
70 @property
71 def can_configure_blueprints(self):
72- """Can the user configure blueprints for the `ISpecificationTarget`."""
73+ """Can the user configure blueprints for the `ISpecificationTarget`.
74+ """
75 target = self.context
76 if IProduct.providedBy(target) or IDistribution.providedBy(target):
77 return check_permission('launchpad.Edit', self.context)
78
79=== modified file 'lib/lp/blueprints/browser/tests/test_specificationtarget.py'
80--- lib/lp/blueprints/browser/tests/test_specificationtarget.py 2010-09-14 15:50:16 +0000
81+++ lib/lp/blueprints/browser/tests/test_specificationtarget.py 2010-09-14 19:01:23 +0000
82@@ -13,6 +13,7 @@
83 ISpecificationTarget,
84 )
85 from lp.app.enums import ServiceUsage
86+from lp.blueprints.browser.specificationtarget import HasSpecificationsView
87 from lp.blueprints.publisher import BlueprintsLayer
88 from lp.testing import (
89 login_person,
90@@ -103,53 +104,65 @@
91 def setUp(self):
92 super(TestHasSpecificationsTemplates, self).setUp()
93 self.user = self.factory.makePerson()
94- self.product = self.factory.makeProduct()
95- self.naked_product = removeSecurityProxy(self.product)
96 login_person(self.user)
97
98- def test_not_configured(self):
99- self.naked_product.blueprints_usage = ServiceUsage.UNKNOWN
100- view = create_view(
101- self.product,
102- '+specs',
103- layer=BlueprintsLayer,
104- principal=self.user)
105- self.assertEqual(
106- view.not_launchpad_template.filename,
107- view.template.filename)
108-
109- def test_external(self):
110- self.naked_product.blueprints_usage = ServiceUsage.EXTERNAL
111- view = create_view(
112- self.product,
113- '+specs',
114- layer=BlueprintsLayer,
115- principal=self.user)
116- self.assertEqual(
117- view.not_launchpad_template.filename,
118- view.template.filename)
119-
120- def test_not_applicable(self):
121- self.naked_product.blueprints_usage = ServiceUsage.NOT_APPLICABLE
122- view = create_view(
123- self.product,
124- '+specs',
125- layer=BlueprintsLayer,
126- principal=self.user)
127- self.assertEqual(
128- view.not_launchpad_template.filename,
129- view.template.filename)
130-
131- def test_on_launchpad(self):
132- self.naked_product.blueprints_usage = ServiceUsage.LAUNCHPAD
133- view = create_view(
134- self.product,
135- '+specs',
136- layer=BlueprintsLayer,
137- principal=self.user)
138- self.assertEqual(
139- view.default_template.filename,
140- view.template.filename)
141+ def _test_templates_for_configuration(self, target, context=None):
142+ if context is None:
143+ context = target
144+ naked_target = removeSecurityProxy(target)
145+ test_configurations = [
146+ ServiceUsage.UNKNOWN,
147+ ServiceUsage.EXTERNAL,
148+ ServiceUsage.NOT_APPLICABLE,
149+ ServiceUsage.LAUNCHPAD,
150+ ]
151+ correct_templates = [
152+ HasSpecificationsView.not_launchpad_template.filename,
153+ HasSpecificationsView.not_launchpad_template.filename,
154+ HasSpecificationsView.not_launchpad_template.filename,
155+ HasSpecificationsView.default_template.filename,
156+ ]
157+ used_templates = list()
158+ for config in test_configurations:
159+ naked_target.blueprints_usage = config
160+ view = create_view(
161+ context,
162+ '+specs',
163+ layer=BlueprintsLayer,
164+ principal=self.user)
165+ used_templates.append(view.template.filename)
166+ self.assertEqual(correct_templates, used_templates)
167+
168+ def test_product(self):
169+ product = self.factory.makeProduct()
170+ self._test_templates_for_configuration(product)
171+
172+ def test_product_series(self):
173+ product = self.factory.makeProduct()
174+ product_series = self.factory.makeProductSeries(product=product)
175+ self._test_templates_for_configuration(
176+ target=product,
177+ context=product_series)
178+
179+ def test_distribution(self):
180+ distribution = self.factory.makeDistribution()
181+ self._test_templates_for_configuration(distribution)
182+
183+ def test_distroseries(self):
184+ distribution = self.factory.makeDistribution()
185+ distro_series = self.factory.makeDistroSeries(
186+ distribution=distribution)
187+ self._test_templates_for_configuration(
188+ target=distribution,
189+ context=distro_series)
190+
191+ def test_projectgroup(self):
192+ project = self.factory.makeProject()
193+ product1 = self.factory.makeProduct(project=project)
194+ product2 = self.factory.makeProduct(project=project)
195+ self._test_templates_for_configuration(
196+ target=product1,
197+ context=project)
198
199
200 class TestHasSpecificationsConfiguration(TestCaseWithFactory):
201@@ -167,7 +180,7 @@
202 view = create_initialized_view(product, '+specs')
203 self.assertEqual(True, view.can_configure_blueprints)
204
205- def test_cannot_configure_blueprints_distribution_no_edit_permission(self):
206+ def test_cant_configure_blueprints_distribution_no_edit_permission(self):
207 distribution = self.factory.makeDistribution()
208 view = create_initialized_view(distribution, '+specs')
209 self.assertEqual(False, view.can_configure_blueprints)
210@@ -178,12 +191,13 @@
211 view = create_initialized_view(distribution, '+specs')
212 self.assertEqual(True, view.can_configure_blueprints)
213
214- def test_cannot_configure_blueprints_projectgroup_with_edit_permission(self):
215+ def test_cannot_configure_blueprints_projectgroup(self):
216 project_group = self.factory.makeProject()
217 login_person(project_group.owner)
218 view = create_initialized_view(project_group, '+specs')
219 self.assertEqual(False, view.can_configure_blueprints)
220
221+
222 def test_suite():
223 suite = unittest.TestSuite()
224 suite.addTest(unittest.TestLoader().loadTestsFromName(__name__))
225
226=== modified file 'lib/lp/blueprints/templates/unknown-specs.pt'
227--- lib/lp/blueprints/templates/unknown-specs.pt 2010-09-14 16:03:51 +0000
228+++ lib/lp/blueprints/templates/unknown-specs.pt 2010-09-14 19:07:36 +0000
229@@ -9,29 +9,38 @@
230
231 <body>
232
233-<div metal:fill-slot="main"
234- tal:define="specs view/specs;
235- has_any_specs view/has_any_specifications;
236- blueprints_usage view/context/blueprints_usage">
237+<div metal:fill-slot="main">
238 <div class="top-portlet">
239 <div id="specs-unknown">
240 <strong>
241- <p tal:condition="blueprints_usage/enumvalue:EXTERNAL">
242- <tal:project replace="view/context/displayname" /> does not use Launchpad
243- for planning or documentation.
244- </p>
245- <p tal:condition="blueprints_usage/enumvalue:NOT_APPLICABLE">
246- <tal:project replace="view/context/displayname" /> does not track
247- use feature planning or documentation.
248- </p>
249- <p tal:condition="blueprints_usage/enumvalue:UNKNOWN">
250- Launchpad does not know how
251- <tal:project replace="view/context/displayname" /> tracks feature
252- planning or documentation.
253- </p>
254+ <div tal:omit-tag tal:condition="view/is_project">
255+ <p>
256+ Launchpad does not know how
257+ <tal:project replace="view/context/displayname" /> tracks feature
258+ planning or documentation.
259+ </p>
260+ </div>
261+ <div tal:omit-tag tal:condition="not:view/is_project">
262+ <div tal:omit-tag tal:define="blueprints_usage view/context/blueprints_usage">
263+ <p tal:condition="blueprints_usage/enumvalue:EXTERNAL">
264+ <tal:project replace="view/context/displayname" /> does not use Launchpad
265+ for planning or documentation.
266+ </p>
267+ <p tal:condition="blueprints_usage/enumvalue:NOT_APPLICABLE">
268+ <tal:project replace="view/context/displayname" /> does not track
269+ use feature planning or documentation.
270+ </p>
271+ <p tal:condition="blueprints_usage/enumvalue:UNKNOWN">
272+ Launchpad does not know how
273+ <tal:project replace="view/context/displayname" /> tracks feature
274+ planning or documentation.
275+ </p>
276+ </div>
277+ </div>
278 </strong>
279
280- <p id="wiki-fallback"
281+ <div tal:omit-tag tal:condition="view/has_wiki">
282+ <p id="wiki-fallback"
283 tal:define="wiki view/context/wikiurl"
284 tal:condition="wiki">
285 <tal:project replace="view/context/displayname" /> has a wiki, which
286@@ -40,8 +49,9 @@
287 <tal:project replace="view/context/displayname" /> wiki
288 </a>
289 </p>
290+ </div>
291 </div>
292-
293+
294 <p id="configure-support"
295 tal:condition="view/can_configure_blueprints">
296 Launchpad Blueprints allow your project to track feature planning and
297
298=== modified file 'lib/lp/registry/browser/distribution.py'
299--- lib/lp/registry/browser/distribution.py 2010-09-13 12:09:30 +0000
300+++ lib/lp/registry/browser/distribution.py 2010-09-14 19:01:42 +0000
301@@ -342,6 +342,7 @@
302 'announcements',
303 'ppas',
304 'configure_answers',
305+ 'configure_blueprints',
306 ]
307
308 @enabled_with_permission('launchpad.Edit')
309@@ -453,6 +454,12 @@
310 summary = 'Allow users to ask questions on this project'
311 return Link('+edit', text, summary, icon='edit')
312
313+ @enabled_with_permission('launchpad.Edit')
314+ def configure_blueprints(self):
315+ text = 'Configure blueprints'
316+ summary = 'Enable tracking of specifications and meetings'
317+ return Link('+edit', text, summary, icon='edit')
318+
319
320 class DerivativeDistributionOverviewMenu(DistributionOverviewMenu):
321
Revision history for this message
Curtis Hovey (sinzui) wrote :

On Tue, 2010-09-14 at 19:16 +0000, j.c.sackett wrote:
> Project groups are tricky--right now I have it defaulting to the old
> style, so that any blueprints made for any project will be shown.
> Given how the earlier question of whether or not to continue showing
> blueprints if has_any_specifications was True, I think it is
> appropriate to only turn this on if at least one product has LAUNCHPAD
> as its usage setting.

I do not this this rule is right. PillarView that renders the
Involvement Portlet and the "Register a Blueprint" link only shows the
link on a project group if a sub project has enabled it. The involvement
portlet is controlled by the pillar owners and its links are enabled to
attack contributors to the applications the pillar uses.

We do not want the Involvement portlet to contradict the app page, which
is the reason we need to disable apps for unknown, not applicable, and
external (most of the time). Answers uses the involvement portlet to
decide the state to ensure it never contradicts. I expect all the apps
do do something similar so that we have a DRY implementation.

        involvement = getMultiAdapter(
            (self.context, self.request), name='+get-involved')
        if service_uses_launchpad(involvement.answers_usage):
            # enable the listing.

--
__Curtis C. Hovey_________
http://launchpad.net/

Revision history for this message
j.c.sackett (jcsackett) wrote :

I like the multiAdapter strategy you showed and have put it in place of the for loop.

Revision history for this message
Guilherme Salgado (salgado) wrote :

Hi Jon,

This looks good, and I only have a couple suggestions.

s/use// @ "[...] does not track use feature planning or documentation."

And for bonus points you could change the 'Configure blueprints' text according to the state the context is in. For example, in the UNKNOWN state, it could be changed to "Tell us how <project> tracks feature planning and documentation". Similarly, for the EXTERNAL (and maybe NOT_APPLICABLE as well?) state, it could be "Use Launchpad to track feature planning and documentation", although that's a bit of a lie as there are other things you can do by following that link (but then it should be a worthy tradeoff if our goal is to get people to use blueprints).

review: Approve (ui*)
Revision history for this message
Curtis Hovey (sinzui) wrote :

I think this is good to land, with the fixing of some link test. I suspect there are some other issues we will see when this feature is used with real data.

Thanks Salgado for seeing the problem in the enums. I see a similar issue with this link test: 'Enable tracking of specifications and meetings'. meetings, AKA sprints do not belong to projects or teams. They are always available because they are a cross-project/cross-team feature. The link is enabling the project to use Launcpads feature planning and documentation tracking features.

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

Doh. My comment was intended to be UI approve.

review: Approve (ui)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/blueprints/browser/configure.zcml'
2--- lib/lp/blueprints/browser/configure.zcml 2010-07-27 17:17:59 +0000
3+++ lib/lp/blueprints/browser/configure.zcml 2010-09-17 14:23:57 +0000
4@@ -53,8 +53,7 @@
5 name="+mdz-specs.csv"
6 attribute="mdzCsv"/>
7 <browser:page
8- name="+specs"
9- template="../templates/hasspecifications-specs.pt"/>
10+ name="+specs"/>
11 <browser:page
12 name="+portlet-latestspecs"
13 template="../templates/specificationtarget-portlet-latestspecs.pt"/>
14@@ -500,8 +499,7 @@
15 class="lp.blueprints.browser.specificationtarget.HasSpecificationsView"
16 permission="zope.Public">
17 <browser:page
18- name="+specs"
19- template="../templates/hasspecifications-specs.pt"/>
20+ name="+specs"/>
21 </browser:pages>
22 <browser:page
23 for="lp.blueprints.interfaces.specificationtarget.ISpecificationGoal"
24@@ -532,8 +530,7 @@
25 class="lp.blueprints.browser.specificationtarget.HasSpecificationsView"
26 permission="zope.Public">
27 <browser:page
28- name="+specs"
29- template="../templates/hasspecifications-specs.pt"/>
30+ name="+specs"/>
31 <browser:page
32 name="+portlet-latestspecs"
33 template="../templates/specificationtarget-portlet-latestspecs.pt"/>
34@@ -583,8 +580,7 @@
35 facet="specifications"
36 permission="zope.Public">
37 <browser:page
38- name="+specs"
39- template="../templates/hasspecifications-specs.pt"/>
40+ name="+specs"/>
41 <browser:page
42 name="+portlet-latestspecs"
43 template="../templates/specificationtarget-portlet-latestspecs.pt"/>
44
45=== modified file 'lib/lp/blueprints/browser/specificationtarget.py'
46--- lib/lp/blueprints/browser/specificationtarget.py 2010-09-14 23:42:27 +0000
47+++ lib/lp/blueprints/browser/specificationtarget.py 2010-09-17 14:23:57 +0000
48@@ -15,7 +15,11 @@
49
50 from operator import itemgetter
51
52-from zope.component import queryMultiAdapter
53+from z3c.ptcompat import ViewPageTemplateFile
54+from zope.component import (
55+ getMultiAdapter,
56+ queryMultiAdapter,
57+ )
58
59 from canonical.config import config
60 from canonical.launchpad import _
61@@ -25,13 +29,19 @@
62 canonical_url,
63 LaunchpadView,
64 )
65+from canonical.launchpad.webapp.authorization import check_permission
66 from canonical.launchpad.webapp.batching import BatchNavigator
67 from canonical.launchpad.webapp.breadcrumb import Breadcrumb
68 from canonical.launchpad.webapp.menu import (
69 enabled_with_permission,
70 Link,
71 )
72-from canonical.lazr.utils import smartquote
73+from canonical.lazr.utils import (
74+ safe_hasattr,
75+ smartquote,
76+ )
77+from lp.app.enums import service_uses_launchpad
78+from lp.app.interfaces.launchpad import IServiceUsage
79 from lp.blueprints.interfaces.specification import (
80 SpecificationFilter,
81 SpecificationSort,
82@@ -131,21 +141,76 @@
83 is_project = False
84 is_series = False
85 is_sprint = False
86+ has_wiki = False
87 has_drivers = False
88
89+ # Templates for the various conditions of blueprints:
90+ # * On Launchpad
91+ # * External
92+ # * Disabled
93+ # * Unknown
94+ default_template = ViewPageTemplateFile(
95+ '../templates/hasspecifications-specs.pt')
96+ not_launchpad_template = ViewPageTemplateFile(
97+ '../templates/unknown-specs.pt')
98+
99+ @property
100+ def template(self):
101+ # Check for the magical "index" added by the browser:page template
102+ # machinery. If it exists this is actually the
103+ # zope.app.pagetemplate.simpleviewclass.simple class that is magically
104+ # mixed in by the browser:page zcml directive the template defined in
105+ # the directive should be used.
106+ if safe_hasattr(self, 'index'):
107+ return super(HasSpecificationsView, self).template
108+
109+ # Sprints and Persons don't have a usage enum for blueprints, so we
110+ # have to fallback to the default.
111+ if (ISprint.providedBy(self.context)
112+ or IPerson.providedBy(self.context)):
113+ return self.default_template
114+
115+ # ProjectGroups are a special case, as their products may be a
116+ # combination of usage settings. To deal with this, check all
117+ # products via the involvment menu.
118+ if (IProjectGroup.providedBy(self.context)
119+ or IProjectGroupSeries.providedBy(self.context)):
120+ involvement = getMultiAdapter(
121+ (self.context, self.request),
122+ name='+get-involved')
123+ if service_uses_launchpad(involvement.blueprints_usage):
124+ return self.default_template
125+ else:
126+ return self.not_launchpad_template
127+
128+ # Otherwise, determine usage and provide the correct template.
129+ service_usage = IServiceUsage(self.context)
130+ if service_uses_launchpad(service_usage.blueprints_usage):
131+ return self.default_template
132+ else:
133+ return self.not_launchpad_template
134+
135+ def render(self):
136+ return self.template()
137+
138 # XXX: jsk: 2007-07-12 bug=173972: This method might be improved by
139 # replacing the conditional execution with polymorphism.
140 def initialize(self):
141 if IPerson.providedBy(self.context):
142 self.is_person = True
143- elif (IDistribution.providedBy(self.context) or
144- IProduct.providedBy(self.context)):
145- self.is_target = True
146- self.is_pillar = True
147+ elif IDistribution.providedBy(self.context):
148+ self.is_target = True
149+ self.is_pillar = True
150+ self.show_series = True
151+ elif IProduct.providedBy(self.context):
152+ self.is_target = True
153+ self.is_pillar = True
154+ self.has_wiki = True
155 self.show_series = True
156 elif IProjectGroup.providedBy(self.context):
157 self.is_project = True
158 self.is_pillar = True
159+ self.has_wiki = True
160 self.show_target = True
161 self.show_series = True
162 elif IProjectGroupSeries.providedBy(self.context):
163@@ -170,6 +235,16 @@
164 size=config.launchpad.default_batch_size)
165
166 @property
167+ def can_configure_blueprints(self):
168+ """Can the user configure blueprints for the `ISpecificationTarget`.
169+ """
170+ target = self.context
171+ if IProduct.providedBy(target) or IDistribution.providedBy(target):
172+ return check_permission('launchpad.Edit', self.context)
173+ else:
174+ return False
175+
176+ @property
177 def label(self):
178 mapping = {'name': self.context.displayname}
179 if self.is_person:
180@@ -199,7 +274,7 @@
181 'distroseries',
182 'direction_approved',
183 'man_days',
184- 'delivery'
185+ 'delivery',
186 ]
187 def dbschema(item):
188 """Format a dbschema sortably for a spreadsheet."""
189
190=== modified file 'lib/lp/blueprints/browser/tests/test_specificationtarget.py'
191--- lib/lp/blueprints/browser/tests/test_specificationtarget.py 2010-09-15 00:07:54 +0000
192+++ lib/lp/blueprints/browser/tests/test_specificationtarget.py 2010-09-17 14:23:57 +0000
193@@ -1,8 +1,12 @@
194-# Copyright 2009 Canonical Ltd. This software is licensed under the
195+# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
196 # GNU Affero General Public License version 3 (see the file LICENSE).
197
198 __metaclass__ = type
199
200+import unittest
201+
202+from zope.security.proxy import removeSecurityProxy
203+
204 from canonical.launchpad.webapp.batching import BatchNavigator
205 from canonical.launchpad.testing.pages import find_tag_by_id
206 from canonical.testing.layers import DatabaseFunctionalLayer
207@@ -10,6 +14,8 @@
208 IHasSpecifications,
209 ISpecificationTarget,
210 )
211+from lp.app.enums import ServiceUsage
212+from lp.blueprints.browser.specificationtarget import HasSpecificationsView
213 from lp.blueprints.publisher import BlueprintsLayer
214 from lp.testing import (
215 login_person,
216@@ -53,7 +59,7 @@
217 self.verify_view(context, 'sprints/%s' % context.name)
218
219
220-class TestHasSpecificationsView(TestCaseWithFactory):
221+class TestHasSpecificationsViewInvolvement(TestCaseWithFactory):
222 """Test specification menus links."""
223 layer = DatabaseFunctionalLayer
224
225@@ -71,10 +77,17 @@
226
227 def test_specificationtarget(self):
228 context = self.factory.makeProduct(name='almond')
229+ naked_product = removeSecurityProxy(context)
230+ naked_product.blueprints_usage = ServiceUsage.LAUNCHPAD
231 self.verify_involvment(context)
232
233 def test_adaptable_to_specificationtarget(self):
234+ # A project should adapt to the products within to determine
235+ # involvment.
236 context = self.factory.makeProject(name='hazelnut')
237+ product = self.factory.makeProduct(project=context)
238+ naked_product = removeSecurityProxy(product)
239+ naked_product.blueprints_usage = ServiceUsage.LAUNCHPAD
240 self.verify_involvment(context)
241
242 def test_sprint(self):
243@@ -128,4 +141,115 @@
244 find_tag_by_id(content, 'upper-batch-nav-batchnav-next')['class'])
245 self.assertEqual('next',
246 find_tag_by_id(content, 'lower-batch-nav-batchnav-next')['class'])
247-
248+
249+
250+class TestHasSpecificationsTemplates(TestCaseWithFactory):
251+ """Tests the selection of templates based on blueprints usage."""
252+
253+ layer = DatabaseFunctionalLayer
254+
255+ def setUp(self):
256+ super(TestHasSpecificationsTemplates, self).setUp()
257+ self.user = self.factory.makePerson()
258+ login_person(self.user)
259+
260+ def _test_templates_for_configuration(self, target, context=None):
261+ if context is None:
262+ context = target
263+ naked_target = removeSecurityProxy(target)
264+ test_configurations = [
265+ ServiceUsage.UNKNOWN,
266+ ServiceUsage.EXTERNAL,
267+ ServiceUsage.NOT_APPLICABLE,
268+ ServiceUsage.LAUNCHPAD,
269+ ]
270+ correct_templates = [
271+ HasSpecificationsView.not_launchpad_template.filename,
272+ HasSpecificationsView.not_launchpad_template.filename,
273+ HasSpecificationsView.not_launchpad_template.filename,
274+ HasSpecificationsView.default_template.filename,
275+ ]
276+ used_templates = list()
277+ for config in test_configurations:
278+ naked_target.blueprints_usage = config
279+ view = create_view(
280+ context,
281+ '+specs',
282+ layer=BlueprintsLayer,
283+ principal=self.user)
284+ used_templates.append(view.template.filename)
285+ self.assertEqual(correct_templates, used_templates)
286+
287+ def test_product(self):
288+ product = self.factory.makeProduct()
289+ self._test_templates_for_configuration(product)
290+
291+ def test_product_series(self):
292+ product = self.factory.makeProduct()
293+ product_series = self.factory.makeProductSeries(product=product)
294+ self._test_templates_for_configuration(
295+ target=product,
296+ context=product_series)
297+
298+ def test_distribution(self):
299+ distribution = self.factory.makeDistribution()
300+ self._test_templates_for_configuration(distribution)
301+
302+ def test_distroseries(self):
303+ distribution = self.factory.makeDistribution()
304+ distro_series = self.factory.makeDistroSeries(
305+ distribution=distribution)
306+ self._test_templates_for_configuration(
307+ target=distribution,
308+ context=distro_series)
309+
310+ def test_projectgroup(self):
311+ project = self.factory.makeProject()
312+ product1 = self.factory.makeProduct(project=project)
313+ product2 = self.factory.makeProduct(project=project)
314+ self._test_templates_for_configuration(
315+ target=product1,
316+ context=project)
317+
318+
319+class TestHasSpecificationsConfiguration(TestCaseWithFactory):
320+
321+ layer = DatabaseFunctionalLayer
322+
323+ def test_cannot_configure_blueprints_product_no_edit_permission(self):
324+ product = self.factory.makeProduct()
325+ view = create_initialized_view(product, '+specs')
326+ self.assertEqual(False, view.can_configure_blueprints)
327+
328+ def test_can_configure_blueprints_product_with_edit_permission(self):
329+ product = self.factory.makeProduct()
330+ login_person(product.owner)
331+ view = create_initialized_view(product, '+specs')
332+ self.assertEqual(True, view.can_configure_blueprints)
333+
334+ def test_cant_configure_blueprints_distribution_no_edit_permission(self):
335+ distribution = self.factory.makeDistribution()
336+ view = create_initialized_view(distribution, '+specs')
337+ self.assertEqual(False, view.can_configure_blueprints)
338+
339+ def test_can_configure_blueprints_distribution_with_edit_permission(self):
340+ distribution = self.factory.makeDistribution()
341+ login_person(distribution.owner)
342+ view = create_initialized_view(distribution, '+specs')
343+ self.assertEqual(True, view.can_configure_blueprints)
344+
345+ def test_cannot_configure_blueprints_projectgroup(self):
346+ project_group = self.factory.makeProject()
347+ login_person(project_group.owner)
348+ view = create_initialized_view(project_group, '+specs')
349+ self.assertEqual(False, view.can_configure_blueprints)
350+
351+
352+def test_suite():
353+ suite = unittest.TestSuite()
354+ suite.addTest(unittest.TestLoader().loadTestsFromName(__name__))
355+ return suite
356+
357+
358+if __name__ == '__main__':
359+ unittest.TextTestRunner().run(test_suite())
360
361=== modified file 'lib/lp/blueprints/stories/blueprints/xx-creation.txt'
362--- lib/lp/blueprints/stories/blueprints/xx-creation.txt 2010-08-26 02:30:06 +0000
363+++ lib/lp/blueprints/stories/blueprints/xx-creation.txt 2010-09-17 14:23:57 +0000
364@@ -1,7 +1,26 @@
365-= Creating Blueprints =
366-
367-
368-== Introduction ==
369+Creating Blueprints
370+===================
371+
372+Set Up
373+------
374+A number of tests in this need a product with blueprints enabled, so we'll
375+enable them on bazaar, firefox, and jokosher.
376+
377+ >>> from zope.component import getUtility
378+ >>> from lp.app.enums import ServiceUsage
379+ >>> from lp.registry.interfaces.product import IProductSet
380+ >>> login('admin@canonical.com')
381+ >>> bazaar = getUtility(IProductSet).getByName('bzr')
382+ >>> bazaar.blueprints_usage = ServiceUsage.LAUNCHPAD
383+ >>> firefox = getUtility(IProductSet).getByName('firefox')
384+ >>> firefox.blueprints_usage = ServiceUsage.LAUNCHPAD
385+ >>> jokosher = getUtility(IProductSet).getByName('jokosher')
386+ >>> jokosher.blueprints_usage = ServiceUsage.LAUNCHPAD
387+ >>> transaction.commit()
388+ >>> logout()
389+
390+Introduction
391+------------
392
393 Users can register blueprints from many locations in Launchpad. To start with,
394 it's possible to register a blueprint from the Blueprints home page. However,
395@@ -15,7 +34,9 @@
396 * a sprint
397
398
399-== The blueprint registration form ==
400+The blueprint registration form
401+-------------------------------
402+
403
404 Launchpad provides a dedicated form page for users to register blueprints.
405 Generally speaking, users can navigate to this form from each of the supported
406@@ -28,13 +49,15 @@
407 we'll use extra care when its necessary to demonstrate that they both exist.
408
409
410-== Navigating to the blueprint registration form ==
411+Navigating to the blueprint registration form
412+---------------------------------------------
413
414 We'll start by demonstrating that users can navigate to the blueprint
415 registration form from each of the supported locations.
416
417
418-=== From the Blueprints home page ===
419+From the Blueprints home page
420+.............................
421
422 Starting from the Blueprints home page:
423
424@@ -46,7 +69,8 @@
425 <a href="+new" id="addspec"> <img alt="Register a blueprint"...
426
427
428-=== From a distribution ===
429+From a distribution
430+...................
431
432 Starting from the Ubuntu distribution page:
433
434@@ -68,7 +92,8 @@
435 class="menu-link-new...>Register a blueprint</a>
436
437
438-=== From a distribution series ===
439+From a distribution series
440+..........................
441
442 Starting from the Ubuntu Hoary distribution series page:
443
444@@ -83,7 +108,8 @@
445 class="menu-link-new...>Register a blueprint</a>
446
447
448-=== From a product ===
449+From a product
450+..............
451
452 Starting from the Bazaar product page:
453
454@@ -109,7 +135,8 @@
455 >>> print extract_text(find_main_content(user_browser.contents))
456 Register a new blueprint...
457
458-=== From a product series ===
459+From a product series
460+.....................
461
462 Starting from the Mozilla Firefox product series page:
463
464@@ -125,7 +152,8 @@
465 class="menu-link-new...>Register a blueprint</a>
466
467
468-=== From a project ===
469+From a project
470+..............
471
472 Starting from the Mozilla project page:
473
474@@ -140,7 +168,8 @@
475 class="menu-link-new...>Register a blueprint</a>
476
477
478-=== From a sprint ===
479+From a sprint
480+.............
481
482 Starting from the Future Mega Meeting sprint page:
483
484@@ -155,14 +184,16 @@
485 class="menu-link-new...>Register a blueprint</a>
486
487
488-== Registering a blueprint ==
489+Registering a blueprint
490+-----------------------
491
492 The blueprint registration form allows users to register a blueprint. The
493 appearance and behaviour of the form depends on where the user has navigated
494 from.
495
496
497-=== Registering a blueprint from the Blueprints home page ===
498+Registering a blueprint from the Blueprints home page
499+.....................................................
500
501 We'll start from the default blueprint registration form:
502
503@@ -216,7 +247,8 @@
504 Network Magic: Auto Network Detection...
505
506
507-=== Registering a blueprint from a distribution ===
508+Registering a blueprint from a distribution
509+...........................................
510
511 When a blueprint is registered from a distribution, the new blueprint is
512 automatically targeted to the distribution.
513@@ -239,7 +271,8 @@
514 Network Magic: Auto Network Detection...
515
516
517-=== Registering a blueprint from a distribution series ===
518+Registering a blueprint from a distribution series
519+..................................................
520
521 When a blueprint is registered from a distribution series, the new blueprint
522 is automatically targeted to the parent distribution. In addition, Launchpad
523@@ -326,7 +359,8 @@
524 Series goal: Accepted for hoary
525
526
527-=== Registering a blueprint from a product ===
528+Registering a blueprint from a product
529+......................................
530
531 When a blueprint is registered from a product, the new blueprint is
532 automatically targeted to the product.
533@@ -355,7 +389,8 @@
534 SVG Support...
535
536
537-=== Registering a blueprint from a product series ===
538+Registering a blueprint from a product series
539+.............................................
540
541 When a blueprint is registered from a product series, the new blueprint is
542 automatically targeted to the parent product. In addition, Launchpad allows
543@@ -437,7 +472,8 @@
544 Series goal: Accepted for 1.0
545
546
547-=== Registering a blueprint from a project ===
548+Registering a blueprint from a project
549+......................................
550
551 Let's register a blueprint from the Mozilla project:
552
553@@ -470,7 +506,8 @@
554 SVG Support...
555
556
557-=== Registering a blueprint from a sprint ===
558+Registering a blueprint from a sprint
559+.....................................
560
561 When a blueprint is registered from a sprint, the new blueprint is
562 automatically proposed as a topic for discussion at the sprint.
563@@ -532,7 +569,8 @@
564 <...darcs-imports-2...
565
566
567-=== Proposing any blueprint as a sprint topic during registration ===
568+Proposing any blueprint as a sprint topic during registration
569+.............................................................
570
571 While blueprints can be registered from sprints directly, it's also possible
572 to propose any blueprint for discussion at a sprint during registration.
573@@ -559,10 +597,11 @@
574 <...spec-for-sprint...>
575
576
577-== Restrictions when registering blueprints ==
578-
579-
580-=== Names must be unique ===
581+Restrictions when registering blueprints
582+----------------------------------------
583+
584+Names must be unique
585+....................
586
587 It's not possible to register a blueprint with the same name as an existing
588 blueprint.
589@@ -602,7 +641,8 @@
590 There is 1 error...already in use by another blueprint...
591
592
593-=== Names must be valid ===
594+Names must be valid
595+...................
596
597 Blueprint names must conform to a set pattern:
598
599@@ -635,7 +675,8 @@
600 Network Magic: Automatic Network Detection...
601
602
603-=== URLs must be unique ===
604+URLs must be unique
605+...................
606
607 It's not possible to register a blueprint with the same URL as an existing
608 blueprint:
609@@ -653,7 +694,8 @@
610 There is 1 error...already registered by another blueprint...
611
612
613-=== Registering blueprints from other locations ===
614+Registering blueprints from other locations
615+...........................................
616
617 There are some locations in Launchpad from which it's not possible to register
618 a blueprint. To start with, it's not possible to register a blueprint from an
619
620=== modified file 'lib/lp/blueprints/stories/blueprints/xx-productseries.txt'
621--- lib/lp/blueprints/stories/blueprints/xx-productseries.txt 2010-08-26 02:30:06 +0000
622+++ lib/lp/blueprints/stories/blueprints/xx-productseries.txt 2010-09-17 14:23:57 +0000
623@@ -2,6 +2,18 @@
624 Targeting to ProductSeries
625 ==========================
626
627+A number of tests in this need a product with blueprints enabled, so we'll
628+enable them on firefox.
629+
630+ >>> from zope.component import getUtility
631+ >>> from lp.app.enums import ServiceUsage
632+ >>> from lp.registry.interfaces.product import IProductSet
633+ >>> login('admin@canonical.com')
634+ >>> firefox = getUtility(IProductSet).getByName('firefox')
635+ >>> firefox.blueprints_usage = ServiceUsage.LAUNCHPAD
636+ >>> transaction.commit()
637+ >>> logout()
638+
639 In terms of feature management for release series and for distroseriess, we
640 want to target one of these specs to the 1.0 series. We will use the "e4x"
641 specification.
642
643=== modified file 'lib/lp/blueprints/stories/standalone/xx-batching.txt'
644--- lib/lp/blueprints/stories/standalone/xx-batching.txt 2009-09-22 10:48:09 +0000
645+++ lib/lp/blueprints/stories/standalone/xx-batching.txt 2010-09-17 14:23:57 +0000
646@@ -24,11 +24,25 @@
647 >>> browser.url
648 'http://launchpad.dev/big-project'
649
650-In the beginning, a project has no blueprints:
651-
652- >>> browser.open("http://blueprints.launchpad.dev/big-project")
653- >>> print extract_text(find_main_content(browser.contents))
654- Blueprints...Register the first blueprint in this project!...
655+In the beginning, a project hasn't had blueprints set up:
656+
657+ >>> browser.open("http://blueprints.launchpad.dev/big-project")
658+ >>> print extract_text(find_main_content(browser.contents))
659+ Blueprints...does not know how...Configure blueprints...
660+
661+But it's easy to change that.
662+
663+ >>> browser.open("http://blueprints.launchpad.dev/big-project/+configure-blueprints")
664+ >>> browser.getControl(name='field.blueprints_usage').value = ['LAUNCHPAD']
665+ >>> browser.getControl('Change').click()
666+ >>> browser.url
667+ 'http://blueprints.launchpad.dev/big-project'
668+
669+Initially the newly enabled feature has no blueprints.
670+
671+ >>> browser.open("http://blueprints.launchpad.dev/big-project")
672+ >>> print extract_text(find_main_content(browser.contents))
673+ Blueprints...first blueprint in this project!...
674
675 We'll go ahead and add just a single blueprint:
676
677
678=== modified file 'lib/lp/blueprints/stories/standalone/xx-index.txt'
679--- lib/lp/blueprints/stories/standalone/xx-index.txt 2009-09-22 16:42:19 +0000
680+++ lib/lp/blueprints/stories/standalone/xx-index.txt 2010-09-17 14:23:57 +0000
681@@ -1,6 +1,20 @@
682 =====================
683 Blueprints index page
684 =====================
685+Enabling Firefox
686+----------------
687+
688+For a product to work in blueprints, it must be active, so we'll activate
689+blueprints for firefox.
690+
691+ >>> from zope.component import getUtility
692+ >>> from lp.app.enums import ServiceUsage
693+ >>> from lp.registry.interfaces.product import IProductSet
694+ >>> login('admin@canonical.com')
695+ >>> firefox = getUtility(IProductSet).getByName('firefox')
696+ >>> firefox.blueprints_usage = ServiceUsage.LAUNCHPAD
697+ >>> transaction.commit()
698+ >>> logout()
699
700 The blueprints index page allows users to search for blueprints within
701 a single Launchpad project or across all projects.
702
703=== modified file 'lib/lp/blueprints/stories/standalone/xx-overview.txt'
704--- lib/lp/blueprints/stories/standalone/xx-overview.txt 2009-11-26 03:06:58 +0000
705+++ lib/lp/blueprints/stories/standalone/xx-overview.txt 2010-09-17 14:23:57 +0000
706@@ -7,6 +7,17 @@
707 blueprints, and also other pages where users can browse through complete
708 lists of blueprints for a given product or distribution.
709
710+A number of tests in this need a product with blueprints enabled, so we'll
711+enable them on firefox.
712+
713+ >>> from zope.component import getUtility
714+ >>> from lp.app.enums import ServiceUsage
715+ >>> from lp.registry.interfaces.product import IProductSet
716+ >>> login('admin@canonical.com')
717+ >>> firefox = getUtility(IProductSet).getByName('firefox')
718+ >>> firefox.blueprints_usage = ServiceUsage.LAUNCHPAD
719+ >>> transaction.commit()
720+ >>> logout()
721
722 Viewing lists of blueprints
723 ===========================
724
725=== modified file 'lib/lp/blueprints/stories/standalone/xx-views.txt'
726--- lib/lp/blueprints/stories/standalone/xx-views.txt 2009-09-22 10:48:09 +0000
727+++ lib/lp/blueprints/stories/standalone/xx-views.txt 2010-09-17 14:23:57 +0000
728@@ -1,6 +1,22 @@
729-== Blueprint views ==
730-
731-=== Viewing current blueprints ===
732+Blueprint views
733+===============
734+
735+Set Up
736+------
737+A number of tests in this need a product with blueprints enabled, so we'll
738+enable them on firefox.
739+
740+ >>> from zope.component import getUtility
741+ >>> from lp.app.enums import ServiceUsage
742+ >>> from lp.registry.interfaces.product import IProductSet
743+ >>> login('admin@canonical.com')
744+ >>> firefox = getUtility(IProductSet).getByName('firefox')
745+ >>> firefox.blueprints_usage = ServiceUsage.LAUNCHPAD
746+ >>> transaction.commit()
747+ >>> logout()
748+
749+Viewing current blueprints
750+--------------------------
751
752 We should be able to see a "+specs" and a "+portlet-latestspecs" view
753 on sprint, product, person, project and distribution.
754@@ -143,7 +159,8 @@
755 ...Activating Usplash...
756
757
758-=== Viewing all blueprints ===
759+Viewing all blueprints
760+----------------------
761
762 From time to time it's useful to review the complete list of all blueprints
763 associated with a blueprint target, including those blueprints that have
764
765=== added file 'lib/lp/blueprints/templates/unknown-specs.pt'
766--- lib/lp/blueprints/templates/unknown-specs.pt 1970-01-01 00:00:00 +0000
767+++ lib/lp/blueprints/templates/unknown-specs.pt 2010-09-17 14:23:57 +0000
768@@ -0,0 +1,93 @@
769+<html
770+ xmlns="http://www.w3.org/1999/xhtml"
771+ xmlns:tal="http://xml.zope.org/namespaces/tal"
772+ xmlns:metal="http://xml.zope.org/namespaces/metal"
773+ xmlns:i18n="http://xml.zope.org/namespaces/i18n"
774+ metal:use-macro="view/macro:page/main_side"
775+ i18n:domain="launchpad"
776+>
777+
778+<body>
779+
780+<div metal:fill-slot="main">
781+ <div class="top-portlet">
782+ <div id="specs-unknown">
783+ <strong>
784+ <div tal:omit-tag tal:condition="view/is_project">
785+ <p>
786+ Launchpad does not know how
787+ <tal:project replace="view/context/displayname" /> tracks feature
788+ planning or documentation.
789+ </p>
790+ </div>
791+
792+ <div tal:omit-tag tal:condition="view/is_series">
793+ <div
794+ tal:omit-tag
795+ tal:define="target python:view.context.product and view.context.product or view.context.distribution;
796+ blueprints_usage target/blueprints_usage">
797+ <p tal:condition="blueprints_usage/enumvalue:EXTERNAL">
798+ <tal:project replace="target/displayname" />'s
799+ <tal:project replace="view/context/displayname" /> series does
800+ not use Launchpap for planning or documentation.
801+ </p>
802+ <p tal:condition="blueprints_usage/enumvalue:NOT_APPLICABLE">
803+ <tal:project replace="target/displayname" />'s
804+ <tal:project replace="view/context/displayname" /> series does not track
805+ feature planning or documentation.
806+ </p>
807+ <p tal:condition="blueprints_usage/enumvalue:UNKNOWN">
808+ Launchpad does not know how
809+ <tal:project replace="target/displayname" />'s
810+ <tal:project replace="view/context/displayname" /> series tracks feature
811+ planning or documentation.
812+ </p>
813+ </div>
814+ </div>
815+
816+ <div tal:omit-tag
817+ tal:condition="python:not (view.is_project or view.is_series)">
818+ <div tal:omit-tag tal:define="blueprints_usage view/context/blueprints_usage">
819+ <p tal:condition="blueprints_usage/enumvalue:EXTERNAL">
820+ <tal:project replace="view/context/displayname" /> does not use launchpad
821+ for planning or documentation.
822+ </p>
823+ <p tal:condition="blueprints_usage/enumvalue:NOT_APPLICABLE">
824+ <tal:project replace="view/context/displayname" /> does not track
825+ feature planning or documentation.
826+ </p>
827+ <p tal:condition="blueprints_usage/enumvalue:UNKNOWN">
828+ launchpad does not know how
829+ <tal:project replace="view/context/displayname" /> tracks feature
830+ planning or documentation.
831+ </p>
832+ </div>
833+ </div>
834+ </strong>
835+
836+ <div tal:omit-tag tal:condition="view/has_wiki">
837+ <p id="wiki-fallback"
838+ tal:define="wiki view/context/wikiurl"
839+ tal:condition="wiki">
840+ <tal:project replace="view/context/displayname" /> has a wiki, which
841+ may be used for feature plannning and documentation.<br />
842+ <a tal:attributes="href view/context/wikiurl">
843+ <tal:project replace="view/context/displayname" /> wiki
844+ </a>
845+ </p>
846+ </div>
847+ </div>
848+
849+ <p id="configure-support"
850+ tal:condition="view/can_configure_blueprints">
851+ Launchpad Blueprints allow your project to track feature planning and
852+ documentation.<br />
853+ <a class="info sprite"
854+ href="https://help.launchpad.net/BlueprintDocumentation">
855+ Read more about tracking feature planning with blueprints</a><br />
856+ <a tal:replace="structure context/menu:overview/configure_blueprints/fmt:link" /><br />
857+ </p>
858+ </div>
859+</div>
860+</body>
861+</html>
862
863=== modified file 'lib/lp/registry/browser/distribution.py'
864--- lib/lp/registry/browser/distribution.py 2010-09-11 19:54:26 +0000
865+++ lib/lp/registry/browser/distribution.py 2010-09-17 14:23:57 +0000
866@@ -342,6 +342,7 @@
867 'announcements',
868 'ppas',
869 'configure_answers',
870+ 'configure_blueprints',
871 ]
872
873 @enabled_with_permission('launchpad.Edit')
874@@ -453,6 +454,12 @@
875 summary = 'Allow users to ask questions on this project'
876 return Link('+edit', text, summary, icon='edit')
877
878+ @enabled_with_permission('launchpad.Edit')
879+ def configure_blueprints(self):
880+ text = 'Configure blueprints'
881+ summary = 'Enable tracking of feature planning.'
882+ return Link('+edit', text, summary, icon='edit')
883+
884
885 class DerivativeDistributionOverviewMenu(DistributionOverviewMenu):
886
887
888=== modified file 'lib/lp/registry/browser/product.py'
889--- lib/lp/registry/browser/product.py 2010-09-03 15:02:39 +0000
890+++ lib/lp/registry/browser/product.py 2010-09-17 14:23:57 +0000
891@@ -547,7 +547,7 @@
892 @enabled_with_permission('launchpad.Edit')
893 def configure_blueprints(self):
894 text = 'Configure blueprints'
895- summary = 'Enable tracking of specifications and meetings'
896+ summary = 'Enable tracking of feature planning.'
897 return Link('+configure-blueprints', text, summary, icon='edit')
898
899 @enabled_with_permission('launchpad.Edit')
900
901=== modified file 'lib/lp/registry/browser/tests/pillar-views.txt'
902--- lib/lp/registry/browser/tests/pillar-views.txt 2010-09-14 21:50:32 +0000
903+++ lib/lp/registry/browser/tests/pillar-views.txt 2010-09-17 14:23:57 +0000
904@@ -36,8 +36,8 @@
905 True
906 >>> print view.answers_usage.name
907 LAUNCHPAD
908- >>> view.translations_usage.name
909- 'UNKNOWN'
910+ >>> print view.translations_usage.name
911+ UNKNOWN
912 >>> print view.blueprints_usage.name
913 UNKNOWN
914 >>> print view.codehosting_usage.name