Merge lp:~bac/launchpad/bug-618148 into lp:launchpad

Proposed by Brad Crittenden
Status: Merged
Approved by: Edwin Grubbs
Approved revision: no longer in the source branch.
Merged at revision: 11443
Proposed branch: lp:~bac/launchpad/bug-618148
Merge into: lp:launchpad
Diff against target: 419 lines (+127/-67)
9 files modified
lib/canonical/launchpad/pagetests/standalone/xx-form-layout.txt (+6/-10)
lib/lp/app/interfaces/launchpad.py (+6/-6)
lib/lp/registry/browser/product.py (+26/-9)
lib/lp/registry/stories/product/xx-product-launchpad-usage.txt (+56/-12)
lib/lp/testing/sampledata.py (+2/-1)
lib/lp/translations/stories/standalone/xx-product-translations.txt (+13/-13)
lib/lp/translations/stories/translationgroups/15-product-translation-group.txt (+1/-3)
lib/lp/translations/stories/translationgroups/46-test-distro-structured-permissions.txt (+7/-5)
lib/lp/translations/stories/translations/55-rosetta-potemplates.txt (+10/-8)
To merge this branch: bzr merge lp:~bac/launchpad/bug-618148
Reviewer Review Type Date Requested Status
Edwin Grubbs (community) code Approve
Steve Kowalik (community) code* Needs Information
Tim Penhey code Pending
Review via email: mp+33600@code.launchpad.net

Commit message

Make +configure-{answers,blueprints,translations} use the ServiceUsage enums rather than a Boolean.

Description of the change

= Summary =

The project configuration progress bar and configuration indicators are
currently based on the official_* booleans and usage of Launchpad apps
is controlled by a boolean. This branch changes the
+configure{answers|blueprints|translations} pages to use the
ServiceUsage enums for multi-state selection.

== Proposed fix ==

As above.

== Pre-implementation notes ==

Talks with Curtis and Jon.

== Implementation details ==

As above.

== Tests ==

bin/test -vvt xx-product-launchpad-usage.txt

== Demo and Q/A ==

Go to https://launchpad.dev/firefox and click on the four configure
links. For blueprints you must go to the app and then click on the
configure link.

= Launchpad lint =

I'll fix these now.

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/translations/stories/standalone/xx-product-translations.txt
  lib/lp/testing/sampledata.py
  lib/lp/registry/browser/product.py
  lib/lp/app/interfaces/launchpad.py
  lib/lp/translations/stories/translations/55-rosetta-potemplates.txt
  lib/lp/registry/stories/product/xx-product-launchpad-usage.txt
  lib/canonical/launchpad/pagetests/standalone/xx-form-layout.txt

lib/lp/translations/stories/translationgroups/46-test-distro-structured-permissions.txt

lib/lp/translations/stories/translationgroups/15-product-translation-group.txt

./lib/lp/translations/stories/standalone/xx-product-translations.txt
       1: narrative uses a moin header.
     169: narrative uses a moin header.
./lib/lp/app/interfaces/launchpad.py
      80: E231 missing whitespace after ','
./lib/lp/translations/stories/translations/55-rosetta-potemplates.txt
       1: narrative uses a moin header.
      70: narrative uses a moin header.
      98: narrative uses a moin header.
     109: source exceeds 78 characters.
     128: want exceeds 78 characters.
     140: want exceeds 78 characters.
     153: want exceeds 78 characters.
     166: want exceeds 78 characters.
./lib/lp/registry/stories/product/xx-product-launchpad-usage.txt
      63: source exceeds 78 characters.
     100: source exceeds 78 characters.

To post a comment you must log in.
Revision history for this message
Steve Kowalik (stevenk) wrote :

Hi,

This looks like a well-thought out change, however, I have a few questions:

* You have lots of use of sampledata in the doctests changed, would you consider not using it?
* You have three classes that look exceedingly similar (ProductConfigureBlueprintsView and so on), can field_names and custom_widget be moved to the base class?

review: Needs Information (code*)
Revision history for this message
Brad Crittenden (bac) wrote :

Hi Steven,

Thanks for the review.

Your idea about refactoring the classes was a good one. It was a little more complicated due to the use of class variables and how custom_widget works but I did get it working and it'll be better long term. Oh, there is some extra funny business there too since the bug tracker set extends this class but then does some funniness.

Incremental diff at http://pastebin.ubuntu.com/483495/

As to sample data, you'll note all of the tests were just changes to existing tests that rely on sample data. I assiduously avoid sample data when constructing new tests but when only modifying old ones it often doesn't make sense to invest the time to rewrite the hole thing. This approach seems to be the one adopted by most teams.

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

Thanks for your comments on IRC, Edwin.

Here is a diff to copy the field before modifying and to make 'field_names' a property. Thanks for the improvements.
http://pastebin.ubuntu.com/483575/

Revision history for this message
Edwin Grubbs (edwin-grubbs) wrote :

Brad, You changes look good.

Steve, I feel I should give you some feedback as a reviewer mentee. I think your review's comment about moving more of the common elements into the base class was very good. You should definitely take a look at Brad's latest changes using copy_field(), since the description modifications would have propagated to other forms if the field isn't copied. Of course, that's a hard one to catch. Eliminating sample data is also a tough call. If a pre-existing test only requires a few lines to be changed, and the branch isn't urgent, then it would be reasonable to force the drive-by changes to made immediately. So, I think your review was good, and you got to learn about some of the esoteric behavior of LaunchpadFormView.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/launchpad/pagetests/standalone/xx-form-layout.txt'
2--- lib/canonical/launchpad/pagetests/standalone/xx-form-layout.txt 2010-06-24 02:21:13 +0000
3+++ lib/canonical/launchpad/pagetests/standalone/xx-form-layout.txt 2010-08-25 18:34:45 +0000
4@@ -96,25 +96,21 @@
5 Checkbox widgets
6 ----------------
7
8-Checkboxes have their label to the right. One example is the checkbox for
9-specifying whether your project uses Launchpad Translations.
10+Checkboxes have their label to the right. Let's look at one example.
11
12 >>> admin_browser.open(
13- ... 'http://launchpad.dev/evolution/+configure-translations')
14- >>> content = find_main_content(admin_browser.contents)
15-
16- >>> print content
17+ ... 'http://launchpad.dev/firefox/+review-license')
18+ >>> print find_tag_by_id(admin_browser.contents, 'launchpad-form-widgets')
19 <...
20 <tr>
21 <td colspan="2">
22- ...
23- <input ... name="field.official_rosetta" type="checkbox" ... />
24- <label for="field.official_rosetta">Translations...</label>...
25+ <div>
26+ <input ... name="field.license_reviewed" type="checkbox" ... />
27+ <label for="field.license_reviewed">Project reviewed</label>...
28 </td>
29 </tr>
30 ...
31
32-
33 Rendering just the form content
34 -------------------------------
35
36
37=== modified file 'lib/lp/app/interfaces/launchpad.py'
38--- lib/lp/app/interfaces/launchpad.py 2010-08-20 20:31:18 +0000
39+++ lib/lp/app/interfaces/launchpad.py 2010-08-25 18:34:45 +0000
40@@ -31,27 +31,27 @@
41 # implies an actual location not an answer of "Launchpad, externally, or
42 # neither."
43 answers_usage = Choice(
44- title=_('Type of service for answers application.'),
45+ title=_('Type of service for answers application'),
46 description=_("Where does this pillar have an Answers forum?"),
47 default=ServiceUsage.UNKNOWN,
48 vocabulary=ServiceUsage)
49 blueprints_usage = Choice(
50- title=_('Type of service for blueprints application.'),
51+ title=_('Type of service for blueprints application'),
52 description=_("Where does this pillar host blueprints?"),
53 default=ServiceUsage.UNKNOWN,
54 vocabulary=ServiceUsage)
55 codehosting_usage = Choice(
56- title=_('Type of service for hosting code.'),
57+ title=_('Type of service for hosting code'),
58 description=_("Where does this pillar host code?"),
59 default=ServiceUsage.UNKNOWN,
60 vocabulary=ServiceUsage)
61 translations_usage = Choice(
62- title=_('Type of service for translations application.'),
63+ title=_('Type of service for translations application'),
64 description=_("Where does this pillar do translations?"),
65 default=ServiceUsage.UNKNOWN,
66 vocabulary=ServiceUsage)
67 bug_tracking_usage = Choice(
68- title=_('Type of service for tracking bugs.'),
69+ title=_('Type of service for tracking bugs'),
70 description=_("Where does this pillar track bugs?"),
71 default=ServiceUsage.UNKNOWN,
72 vocabulary=ServiceUsage)
73@@ -77,7 +77,7 @@
74 title=_('Translations for this project are done in Launchpad'),
75 required=True)
76 official_anything = Bool(
77- title=_('Uses Launchpad for something'),)
78+ title=_('Uses Launchpad for something'))
79 enable_bug_expiration = Bool(
80 title=_('Expire "Incomplete" bug reports when they become inactive'),
81 required=True)
82
83=== modified file 'lib/lp/registry/browser/product.py'
84--- lib/lp/registry/browser/product.py 2010-08-24 10:45:23 +0000
85+++ lib/lp/registry/browser/product.py 2010-08-25 18:34:45 +0000
86@@ -52,6 +52,7 @@
87
88 import pytz
89 from z3c.ptcompat import ViewPageTemplateFile
90+from zope.app.form import CustomWidgetFactory
91 from zope.app.form.browser import (
92 CheckBoxWidget,
93 TextAreaWidget,
94@@ -1399,6 +1400,28 @@
95 class ProductConfigureBase(ReturnToReferrerMixin, LaunchpadEditFormView):
96 implements(IProductEditMenu)
97 schema = IProduct
98+ usage_fieldname = None
99+
100+ def setUpFields(self):
101+ super(ProductConfigureBase, self).setUpFields()
102+ if self.usage_fieldname is not None:
103+ # The usage fields are shared among pillars. But when referring to
104+ # an individual object in Launchpad it is better to call it by its
105+ # real name, i.e. 'project' instead of 'pillar'.
106+ usage_field = self.form_fields.get(self.usage_fieldname)
107+ if usage_field:
108+ usage_field.custom_widget = CustomWidgetFactory(
109+ LaunchpadRadioWidget, orientation='vertical')
110+ # Copy the field or else the description in the interface will
111+ # be modified in-place.
112+ field = copy_field(usage_field.field)
113+ field.description = (
114+ field.description.replace('pillar', 'project'))
115+ usage_field.field = field
116+
117+ @property
118+ def field_names(self):
119+ return [self.usage_fieldname]
120
121 @property
122 def page_title(self):
123@@ -1413,27 +1436,21 @@
124 """View class to configure the Launchpad Blueprints for a project."""
125
126 label = "Configure Blueprints"
127- field_names = [
128- "official_blueprints",
129- ]
130+ usage_fieldname = 'blueprints_usage'
131
132
133 class ProductConfigureTranslationsView(ProductConfigureBase):
134 """View class to configure the Launchpad Translations for a project."""
135
136 label = "Configure Translations"
137- field_names = [
138- "official_rosetta",
139- ]
140+ usage_fieldname = 'translations_usage'
141
142
143 class ProductConfigureAnswersView(ProductConfigureBase):
144 """View class to configure the Launchpad Answers for a project."""
145
146 label = "Configure Answers"
147- field_names = [
148- "official_answers",
149- ]
150+ usage_fieldname = 'answers_usage'
151
152
153 class ProductEditView(ProductLicenseMixin, LaunchpadEditFormView):
154
155=== modified file 'lib/lp/registry/stories/product/xx-product-launchpad-usage.txt'
156--- lib/lp/registry/stories/product/xx-product-launchpad-usage.txt 2010-06-14 20:12:43 +0000
157+++ lib/lp/registry/stories/product/xx-product-launchpad-usage.txt 2010-08-25 18:34:45 +0000
158@@ -23,10 +23,12 @@
159 ...
160 Unauthorized...
161
162-so let's use Sample Person, the registrant of Firefox..
163+Let's use Sample Person, the registrant of Firefox..
164
165+ >>> from lp.testing.sampledata import SAMPLE_PERSON_EMAIL
166 >>> registrant_browser = setupBrowser(
167- ... auth='Basic test@canonical.com:test')
168+ ... auth='Basic %(email)s:%(passwd)s' % dict(
169+ ... email=SAMPLE_PERSON_EMAIL, passwd='test'))
170 >>> registrant_browser.open('http://launchpad.dev/firefox')
171 >>> registrant_browser.getLink('Configure bug tracker').click()
172 >>> registrant_browser.url
173@@ -58,11 +60,14 @@
174 >>> registrant_browser.getLink('Configure translations').click()
175 >>> registrant_browser.url
176 'http://launchpad.dev/firefox/+configure-translations'
177- >>> control = registrant_browser.getControl(
178- ... 'Translations for this project are done in Launchpad')
179- >>> control.selected
180- False
181- >>> control.selected = True
182+ >>> print_radio_button_field(registrant_browser.contents,
183+ ... "translations_usage")
184+ (*) Unknown
185+ ( ) Launchpad
186+ ( ) External
187+ ( ) Not Applicable
188+
189+ >>> registrant_browser.getControl('Launchpad').click()
190 >>> registrant_browser.getControl('Change').click()
191
192 Answers can be disabled.
193@@ -71,11 +76,12 @@
194 >>> registrant_browser.getLink('Configure support tracker').click()
195 >>> registrant_browser.url
196 'http://launchpad.dev/firefox/+configure-answers'
197- >>> control = registrant_browser.getControl(
198- ... 'People can ask questions in Launchpad Answers')
199- >>> control.selected
200- True
201- >>> control.selected = False
202+ >>> print_radio_button_field(registrant_browser.contents, "answers_usage")
203+ ( ) Unknown
204+ (*) Launchpad
205+ ( ) External
206+ ( ) Not Applicable
207+ >>> registrant_browser.getControl('Unknown').click()
208 >>> registrant_browser.getControl('Change').click()
209
210 On the product page, we can see that the product doesn't use any bug
211@@ -85,6 +91,44 @@
212 >>> print extract_text(uses)
213 Uses Launchpad for: Branches and Translations.
214
215+Blueprints can be enabled too, though it isn't easy.
216+
217+ >>> # First go to the Blueprints page.
218+ >>> registrant_browser.open('http://launchpad.dev/firefox')
219+ >>> registrant_browser.getLink('Blueprints').click()
220+ >>> registrant_browser.getLink('Configure blueprints').click()
221+ >>> registrant_browser.url
222+ 'http://blueprints.launchpad.dev/firefox/+configure-blueprints'
223+ >>> print_radio_button_field(registrant_browser.contents,
224+ ... "blueprints_usage")
225+ (*) Unknown
226+ ( ) Launchpad
227+ ( ) External
228+ ( ) Not Applicable
229+ >>> registrant_browser.getControl('Launchpad').click()
230+ >>> registrant_browser.getControl('Change').click()
231+
232+
233+On the product page, we can see that the product doesn't use any bug
234+tracker, not even Launchpad, but does use other apps.
235+
236+ >>> registrant_browser.open('http://launchpad.dev/firefox')
237+ >>> uses = find_tag_by_id(registrant_browser.contents, id='uses')
238+ >>> print extract_text(uses)
239+ Uses Launchpad for: Blueprints, Branches, and Translations.
240+
241+Setting a usage to external is reflected in the "Uses" message.
242+Change translations to external.
243+
244+ >>> registrant_browser.open('http://launchpad.dev/firefox')
245+ >>> registrant_browser.getLink('Configure translations').click()
246+ >>> registrant_browser.getControl('External').click()
247+ >>> registrant_browser.getControl('Change').click()
248+
249+ >>> uses = find_tag_by_id(registrant_browser.contents, id='uses')
250+ >>> print extract_text(uses)
251+ Uses Launchpad for: Blueprints and Branches.
252+
253
254 Tracking bugs by email
255 ----------------------
256
257=== modified file 'lib/lp/testing/sampledata.py'
258--- lib/lp/testing/sampledata.py 2010-08-13 21:30:24 +0000
259+++ lib/lp/testing/sampledata.py 2010-08-25 18:34:45 +0000
260@@ -13,6 +13,7 @@
261 'CHROOT_LIBRARYFILEALIAS',
262 'ADMIN_EMAIL',
263 'COMMERCIAL_ADMIN_EMAIL',
264+ 'SAMPLE_PERSON_EMAIL',
265 'HOARY_DISTROSERIES_NAME',
266 'I386_ARCHITECTURE_NAME',
267 'LAUNCHPAD_DBUSER_NAME',
268@@ -43,7 +44,7 @@
269 NO_PRIVILEGE_EMAIL = 'no-priv@canonical.com'
270 COMMERCIAL_ADMIN_EMAIL = 'commercial-member@canonical.com'
271 ADMIN_EMAIL = 'foo.bar@canonical.com'
272-
273+SAMPLE_PERSON_EMAIL = 'test@canonical.com'
274 # A user that is an admin of ubuntu-team, which has upload rights
275 # to Ubuntu.
276 UBUNTU_DEVELOPER_ADMIN_NAME = 'name16'
277
278=== modified file 'lib/lp/translations/stories/standalone/xx-product-translations.txt'
279--- lib/lp/translations/stories/standalone/xx-product-translations.txt 2010-03-31 20:25:33 +0000
280+++ lib/lp/translations/stories/standalone/xx-product-translations.txt 2010-08-25 18:34:45 +0000
281@@ -1,4 +1,6 @@
282-= Product Translations =
283+====================
284+Product Translations
285+====================
286
287 Each product in Launchpad has a Translations page.
288
289@@ -137,20 +139,17 @@
290
291 If the netapplet project is updated to use Launchpad for translations...
292
293- >>> admin_browser.open('http://translations.launchpad.dev/netapplet')
294- >>> admin_browser.getLink('Overview').click()
295- >>> print admin_browser.title
296- Network Applet in Launchpad
297+ >>> admin_browser.open('http://launchpad.dev/netapplet')
298 >>> admin_browser.getLink('Configure translations').click()
299- >>> admin_browser.getControl(
300- ... 'Translations for this project are done in Launchpad').selected
301- False
302- >>> admin_browser.getControl(
303- ... 'Translations for this project are done in Launchpad'
304- ... ).selected = True
305+ >>> print_radio_button_field(admin_browser.contents, "translations_usage")
306+ (*) Unknown
307+ ( ) Launchpad
308+ ( ) External
309+ ( ) Not Applicable
310+ >>> admin_browser.getControl('Launchpad').click()
311 >>> admin_browser.getControl('Change').click()
312
313-...there are no longer any obsolete entires.
314+...there are no longer any obsolete entries.
315
316 >>> admin_browser.getLink('Translations').click()
317 >>> print admin_browser.title
318@@ -169,7 +168,8 @@
319 Language Untranslated Unreviewed Changed
320
321
322-== Translation recommendation ==
323+Translation recommendation
324+==========================
325
326 The page mentions which product series should be translated.
327
328
329=== modified file 'lib/lp/translations/stories/translationgroups/15-product-translation-group.txt'
330--- lib/lp/translations/stories/translationgroups/15-product-translation-group.txt 2010-03-31 20:25:33 +0000
331+++ lib/lp/translations/stories/translationgroups/15-product-translation-group.txt 2010-08-25 18:34:45 +0000
332@@ -15,9 +15,7 @@
333 >>> print netapplet_owner_browser.title
334 Configure Translations : NetApplet
335
336- >>> netapplet_owner_browser.getControl(
337- ... 'Translations for this project are done in Launchpad'
338- ... ).selected = True
339+ >>> netapplet_owner_browser.getControl('Launchpad').click()
340 >>> netapplet_owner_browser.getControl('Change').click()
341 >>> print netapplet_owner_browser.title
342 Network Applet in Launchpad
343
344=== modified file 'lib/lp/translations/stories/translationgroups/46-test-distro-structured-permissions.txt'
345--- lib/lp/translations/stories/translationgroups/46-test-distro-structured-permissions.txt 2010-03-31 20:25:33 +0000
346+++ lib/lp/translations/stories/translationgroups/46-test-distro-structured-permissions.txt 2010-08-25 18:34:45 +0000
347@@ -1,11 +1,13 @@
348-First, we state that netapplet is using Launchpad Translations.
349+First, we verify that netapplet is using Launchpad Translations.
350
351 >>> admin_browser.open('http://launchpad.dev/netapplet')
352 >>> admin_browser.getLink('Configure translations').click()
353- >>> admin_browser.getControl(
354- ... 'Translations for this project are done in Launchpad'
355- ... ).selected = True
356- >>> admin_browser.getControl('Change').click()
357+ >>> print_radio_button_field(admin_browser.contents, "translations_usage")
358+ ( ) Unknown
359+ (*) Launchpad
360+ ( ) External
361+ ( ) Not Applicable
362+ >>> admin_browser.getLink('Cancel').click()
363 >>> print admin_browser.title
364 Network Applet in Launchpad
365
366
367=== modified file 'lib/lp/translations/stories/translations/55-rosetta-potemplates.txt'
368--- lib/lp/translations/stories/translations/55-rosetta-potemplates.txt 2010-03-16 22:07:45 +0000
369+++ lib/lp/translations/stories/translations/55-rosetta-potemplates.txt 2010-08-25 18:34:45 +0000
370@@ -1,4 +1,6 @@
371-== Legend and headers display ==
372+==========================
373+Legend and headers display
374+==========================
375
376 On template overview page, when user is not logged in (and there is no
377 sufficient GeoIP data), or user has not set his preferred languages,
378@@ -49,9 +51,7 @@
379 # translations.
380 >>> admin_browser.open(
381 ... 'http://launchpad.dev/netapplet/+configure-translations')
382- >>> admin_browser.getControl(
383- ... 'Translations for this project are done in Launchpad'
384- ... ).selected = True
385+ >>> admin_browser.getControl('Launchpad').click()
386 >>> admin_browser.getControl('Change').click()
387 >>> anon_browser.open(
388 ... 'http://translations.launchpad.dev/netapplet/trunk/+pots/'
389@@ -69,7 +69,8 @@
390 True
391
392
393-= The PO Template Views =
394+The PO Template Views
395+=====================
396
397 Carlos is a translator who translates things into Spanish and Catalan.
398 When he looks at available translations for Evolution in Hoary, he
399@@ -97,7 +98,8 @@
400
401 So, everything is fine, and Carlos can sleep calmly.
402
403-== Links to filtered pages ==
404+Links to filtered pages
405+=======================
406
407 The POTemplate overview page shows different statistics: a count of
408 untranslated messages, messages with new, unreviewed suggestions, and
409@@ -108,8 +110,8 @@
410 15 untranslated, 1 unreviewed and 1 changed in LP Spanish translations
411 (all numbers repeated as hidden 'sortkey' values).
412
413- >>> browser.open('http://translations.launchpad.dev/ubuntu/hoary/+source/'+
414- ... 'evolution/+pots/evolution-2.2')
415+ >>> browser.open('http://translations.launchpad.dev/ubuntu/hoary/'+
416+ ... '+source/evolution/+pots/evolution-2.2')
417 >>> spanish_line = find_tag_by_id(browser.contents, 'evolution-2.2_es')
418 >>> print extract_text(spanish_line)
419 Spanish