Merge lp:~wallyworld/launchpad/new-team-picker-enhanced-form into lp:launchpad
- new-team-picker-enhanced-form
- Merge into devel
Status: | Merged | ||||||||
---|---|---|---|---|---|---|---|---|---|
Approved by: | Ian Booth | ||||||||
Approved revision: | no longer in the source branch. | ||||||||
Merged at revision: | 15542 | ||||||||
Proposed branch: | lp:~wallyworld/launchpad/new-team-picker-enhanced-form | ||||||||
Merge into: | lp:launchpad | ||||||||
Diff against target: |
814 lines (+265/-67) 15 files modified
lib/lp/app/javascript/choice.js (+40/-27) lib/lp/app/javascript/picker/team.js (+8/-1) lib/lp/app/javascript/picker/tests/test_personpicker.html (+2/-0) lib/lp/app/javascript/picker/tests/test_team.html (+22/-0) lib/lp/app/javascript/picker/tests/test_team.js (+57/-4) lib/lp/bugs/javascript/tests/test_filebug.js (+16/-12) lib/lp/registry/browser/configure.zcml (+1/-1) lib/lp/registry/browser/distribution.py (+4/-3) lib/lp/registry/browser/pillar.py (+30/-7) lib/lp/registry/browser/product.py (+5/-4) lib/lp/registry/browser/productseries.py (+2/-2) lib/lp/registry/browser/project.py (+4/-3) lib/lp/registry/browser/tests/test_distribution.py (+28/-1) lib/lp/registry/browser/tests/test_product.py (+16/-0) lib/lp/registry/browser/tests/test_projectgroup.py (+30/-2) |
||||||||
To merge this branch: | bzr merge lp:~wallyworld/launchpad/new-team-picker-enhanced-form | ||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Richard Harding (community) | code | Approve | |
Review via email: mp+113020@code.launchpad.net |
Commit message
Enhance the New Team form inside the person picker to use a choice popup for subscription policy, enable New Team link on maintainers and drivers for all pillars.
Description of the change
== Implementation ==
This branch enhances the New Team form inside the person picker to use a choice popup for selecting the team subscription policy. It also ensures the New Team link is enabled on maintainer/driver pickers for all pillars - product, project group, distribution.
The json request cache needed to have the team subscription policy data shoved into it for the choice popup to be wired in. A PillarViewMixin is provided to do this. There was an existing PillarView base class but this should really have been called PillarInvolveme
As a driveby, fix the issue where the header text on the choice popup was displaying the field name with an underscore.
== Demo ==
See screenshot:
http://
Do we want to leave the public/private drop down as is or make that a choice popup too to make it all look consistent? I think it looks a bit funny now with the dropdown and choice popup widgets both being used.
== Tests ==
Add tests for [Product|
Add yui tests for the enhanced new team form.
== Lint ==
Checking for conflicts and issues in changed files.
Linting changed files:
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
Ian Booth (wallyworld) wrote : | # |
Wow, quick review, thanks.
> Thanks Ian. I'm reviewing the code, but leave the question on the select dropdown to others. It seems a bit simple to turn over into the larger UI widget, but there's something to be said for consistant look/feel.
>
I'll land as is and fix the dropdown if needed. It's behind a feature
flag anyway.
>
> #775
> Typo: grupo
>
Perhaps. I used the same project group name as was used in another test
in the same module and thought that maybe the author was using a
humorous name. I'll change both to avoid future questions :-)
>
Preview Diff
1 | === modified file 'lib/lp/app/javascript/choice.js' |
2 | --- lib/lp/app/javascript/choice.js 2012-06-27 14:05:07 +0000 |
3 | +++ lib/lp/app/javascript/choice.js 2012-07-03 07:13:21 +0000 |
4 | @@ -56,54 +56,65 @@ |
5 | widget.render(); |
6 | }; |
7 | |
8 | +// The default configuration used for wiring choice popup widgets. |
9 | +var default_popup_choice_config = { |
10 | + container: Y, |
11 | + render_immediately: true, |
12 | + show_description: false, |
13 | + field_title: null |
14 | +}; |
15 | + |
16 | /** |
17 | * Replace a legacy input widget with a popup choice widget. |
18 | * @param legacy_node the YUI node containing the legacy widget. |
19 | * @param field_name the Launchpad form field name. |
20 | * @param choices the choices for the popup choice widget. |
21 | - * @param show_description whether to show the selected value's description. |
22 | + * @param cfg configuration for the wiring action. |
23 | * @param get_value_fn getter for the legacy widget's value. |
24 | * @param set_value_fn setter for the legacy widget's value. |
25 | */ |
26 | -var wirePopupChoice = function(legacy_node, field_name, choices, |
27 | - show_description, get_value_fn, set_value_fn) { |
28 | +var wirePopupChoice = function(legacy_node, field_name, choices, cfg, |
29 | + get_value_fn, set_value_fn) { |
30 | var choice_descriptions = {}; |
31 | Y.Array.forEach(choices, function(item) { |
32 | choice_descriptions[item.value] = item.description; |
33 | }); |
34 | var initial_field_value = get_value_fn(legacy_node); |
35 | var choice_node = Y.Node.create([ |
36 | - '<span id="' + field_name + '-content"><span class="value"></span>', |
37 | + '<span class="' + field_name + '-content"><span class="value"></span>', |
38 | '<a class="sprite edit editicon action-icon"', |
39 | ' href="#">Edit</a></span>' |
40 | ].join('')); |
41 | - if (show_description) { |
42 | + if (cfg.show_description) { |
43 | choice_node.append(Y.Node.create('<div class="formHelp"></div>')); |
44 | } |
45 | - |
46 | legacy_node.insertBefore(choice_node, legacy_node); |
47 | - if (show_description) { |
48 | + if (cfg.show_description) { |
49 | choice_node.one('.formHelp') |
50 | .set('text', choice_descriptions[initial_field_value]); |
51 | } |
52 | legacy_node.addClass('unseen'); |
53 | - var field_content = Y.one('#' + field_name + '-content'); |
54 | - |
55 | + if (!Y.Lang.isValue(cfg.field_title)) { |
56 | + cfg.field_title = field_name.replace('_', ' '); |
57 | + } |
58 | var choice_edit = new Y.ChoiceSource({ |
59 | - contentBox: field_content, |
60 | + contentBox: choice_node, |
61 | value: initial_field_value, |
62 | - title: 'Set ' + field_name + " as", |
63 | + title: 'Set ' + cfg.field_title + " as", |
64 | items: choices, |
65 | - elementToFlash: field_content |
66 | + elementToFlash: choice_node, |
67 | + zIndex: 1050 |
68 | }); |
69 | - choice_edit.render(); |
70 | + if (cfg.render_immediately) { |
71 | + choice_edit.render(); |
72 | + } |
73 | |
74 | var update_selected_value_css = function(selected_value) { |
75 | Y.Array.each(choices, function(item) { |
76 | if (item.value === selected_value) { |
77 | - field_content.addClass(item.css_class); |
78 | + choice_node.addClass(item.css_class); |
79 | } else { |
80 | - field_content.removeClass(item.css_class); |
81 | + choice_node.removeClass(item.css_class); |
82 | } |
83 | }); |
84 | }; |
85 | @@ -112,21 +123,23 @@ |
86 | var selected_value = choice_edit.get('value'); |
87 | update_selected_value_css(selected_value); |
88 | set_value_fn(legacy_node, selected_value); |
89 | - if (show_description) { |
90 | + if (cfg.show_description) { |
91 | choice_node.one('.formHelp') |
92 | .set('text', choice_descriptions[selected_value]); |
93 | } |
94 | }); |
95 | + return choice_edit; |
96 | }; |
97 | |
98 | /** |
99 | * Replace a drop down combo box with a popup choice selection widget. |
100 | * @param field_name |
101 | * @param choices |
102 | - * @param show_description |
103 | + * @param cfg |
104 | */ |
105 | -namespace.addPopupChoice = function(field_name, choices, show_description) { |
106 | - var legacy_node = Y.one('[id="field.' + field_name + '"]'); |
107 | +namespace.addPopupChoice = function(field_name, choices, cfg) { |
108 | + cfg = Y.merge(default_popup_choice_config, cfg); |
109 | + var legacy_node = cfg.container.one('[id="field.' + field_name + '"]'); |
110 | if (!Y.Lang.isValue(legacy_node)) { |
111 | return; |
112 | } |
113 | @@ -136,19 +149,19 @@ |
114 | var set_fn = function(node, value) { |
115 | node.set('value', value); |
116 | }; |
117 | - wirePopupChoice( |
118 | - legacy_node, field_name, choices, show_description, get_fn, set_fn); |
119 | + return wirePopupChoice( |
120 | + legacy_node, field_name, choices, cfg, get_fn, set_fn); |
121 | }; |
122 | |
123 | /** |
124 | * Replace a radio button group with a popup choice selection widget. |
125 | * @param field_name |
126 | * @param choices |
127 | - * @param show_description |
128 | + * @param cfg |
129 | */ |
130 | -namespace.addPopupChoiceForRadioButtons = function(field_name, choices, |
131 | - show_description) { |
132 | - var legacy_node = Y.one('[name="field.' + field_name + '"]') |
133 | +namespace.addPopupChoiceForRadioButtons = function(field_name, choices, cfg) { |
134 | + cfg = Y.merge(default_popup_choice_config, cfg); |
135 | + var legacy_node = cfg.container.one('[name="field.' + field_name + '"]') |
136 | .ancestor('table.radio-button-widget'); |
137 | if (!Y.Lang.isValue(legacy_node)) { |
138 | return; |
139 | @@ -172,8 +185,8 @@ |
140 | } |
141 | }); |
142 | }; |
143 | - wirePopupChoice( |
144 | - legacy_node, field_name, choices, show_description, get_fn, set_fn); |
145 | + return wirePopupChoice( |
146 | + legacy_node, field_name, choices, cfg, get_fn, set_fn); |
147 | }; |
148 | |
149 | }, "0.1", {"requires": ["lazr.choiceedit", "lp.client.plugins", |
150 | |
151 | === modified file 'lib/lp/app/javascript/picker/team.js' |
152 | --- lib/lp/app/javascript/picker/team.js 2012-07-02 04:16:19 +0000 |
153 | +++ lib/lp/app/javascript/picker/team.js 2012-07-03 07:13:21 +0000 |
154 | @@ -106,6 +106,12 @@ |
155 | e.halt(); |
156 | this.fire(ns.CANCEL_TEAM); |
157 | }, this); |
158 | + this.subscriptionpolicy_edit = Y.lp.app.choice.addPopupChoice( |
159 | + 'subscriptionpolicy', LP.cache.team_subscriptionpolicy_data, { |
160 | + container: container, |
161 | + render_immediately: false, |
162 | + field_title: 'subscription policy' |
163 | + }); |
164 | container.one('.extra-form-buttons').removeClass('hidden'); |
165 | }, |
166 | |
167 | @@ -114,6 +120,7 @@ |
168 | if (form_elements.size() > 0) { |
169 | form_elements.item(0).focus(); |
170 | } |
171 | + this.subscriptionpolicy_edit.render(); |
172 | }, |
173 | |
174 | hide: function() { |
175 | @@ -214,5 +221,5 @@ |
176 | }); |
177 | |
178 | |
179 | -}, "0.1", {"requires": ["base", "node"]}); |
180 | +}, "0.1", {"requires": ["base", "node", "lp.app.choice"]}); |
181 | |
182 | |
183 | === modified file 'lib/lp/app/javascript/picker/tests/test_personpicker.html' |
184 | --- lib/lp/app/javascript/picker/tests/test_personpicker.html 2012-06-26 03:23:20 +0000 |
185 | +++ lib/lp/app/javascript/picker/tests/test_personpicker.html 2012-07-03 07:13:21 +0000 |
186 | @@ -26,6 +26,8 @@ |
187 | |
188 | <!-- Dependencies --> |
189 | <script type="text/javascript" |
190 | + src="../../../../../../build/js/lp/app/choice.js"></script> |
191 | + <script type="text/javascript" |
192 | src="../../../../../../build/js/lp/app/client.js"></script> |
193 | <script type="text/javascript" |
194 | src="../../../../../../build/js/lp/app/lp.js"></script> |
195 | |
196 | === modified file 'lib/lp/app/javascript/picker/tests/test_team.html' |
197 | --- lib/lp/app/javascript/picker/tests/test_team.html 2012-06-26 09:02:51 +0000 |
198 | +++ lib/lp/app/javascript/picker/tests/test_team.html 2012-07-03 07:13:21 +0000 |
199 | @@ -26,12 +26,34 @@ |
200 | |
201 | <!-- Dependencies --> |
202 | <script type="text/javascript" |
203 | + src="../../../../../../build/js/lp/app/choice.js"></script> |
204 | + <script type="text/javascript" |
205 | src="../../../../../../build/js/lp/app/client.js"></script> |
206 | <script type="text/javascript" |
207 | + src="../../../../../../build/js/lp/app/errors.js"></script> |
208 | + <script type="text/javascript" |
209 | src="../../../../../../build/js/lp/app/lp.js"></script> |
210 | <script type="text/javascript" |
211 | src="../../../../../../build/js/lp/app/lazr/lazr.js"></script> |
212 | <script type="text/javascript" |
213 | + src="../../../../../../build/js/lp/app/choiceedit/choiceedit.js"></script> |
214 | + <script type="text/javascript" |
215 | + src="../../../../../../build/js/lp/app/overlay/overlay.js"></script> |
216 | + <script type="text/javascript" |
217 | + src="../../../../../../build/js/lp/app/anim/anim.js"></script> |
218 | + <script type="text/javascript" |
219 | + src="../../../../../../build/js/lp/app/effects/effects.js"></script> |
220 | + <script type="text/javascript" |
221 | + src="../../../../../../build/js/lp/app/expander.js"></script> |
222 | + <script type="text/javascript" |
223 | + src="../../../../../../build/js/lp/app/extras/extras.js"></script> |
224 | + <script type="text/javascript" |
225 | + src="../../../../../../build/js/lp/app/formoverlay/formoverlay.js"></script> |
226 | + <script type="text/javascript" |
227 | + src="../../../../../../build/js/lp/app/formwidgets/resizing_textarea.js"></script> |
228 | + <script type="text/javascript" |
229 | + src="../../../../../../build/js/lp/app/inlineedit/editor.js"></script> |
230 | + <script type="text/javascript" |
231 | src="../../../../../../build/js/lp/app/testing/mockio.js"></script> |
232 | |
233 | <!-- The module under test. --> |
234 | |
235 | === modified file 'lib/lp/app/javascript/picker/tests/test_team.js' |
236 | --- lib/lp/app/javascript/picker/tests/test_team.js 2012-07-02 04:16:19 +0000 |
237 | +++ lib/lp/app/javascript/picker/tests/test_team.js 2012-07-03 07:13:21 +0000 |
238 | @@ -12,9 +12,19 @@ |
239 | |
240 | |
241 | setUp: function() { |
242 | + window.LP = { |
243 | + links: {}, |
244 | + cache: { |
245 | + team_subscriptionpolicy_data: [ |
246 | + {name: 'Moderated', value: 'MODERATED'}, |
247 | + {name: 'Restricted', value: 'RESTRICTED'} |
248 | + ] |
249 | + } |
250 | + }; |
251 | }, |
252 | |
253 | tearDown: function() { |
254 | + delete window.LP; |
255 | delete this.mockio; |
256 | if (this.fixture !== undefined) { |
257 | this.fixture.empty(true); |
258 | @@ -26,10 +36,21 @@ |
259 | }, |
260 | |
261 | _simple_team_form: function() { |
262 | - return '<table><tr><td>' + |
263 | - '<input id="field.name" name="field.name"/>' + |
264 | - '<input id="field.displayname" ' + |
265 | - 'name="field.displayname"/></td></tr></table>'; |
266 | + return [ |
267 | + '<table><tr><td>', |
268 | + '<input id="field.name" name="field.name"/>', |
269 | + '<input id="field.displayname" ', |
270 | + 'name="field.displayname"/>', |
271 | + '<div class="value">', |
272 | + '<select size="1" name="field.subscriptionpolicy" ', |
273 | + 'id="field.subscriptionpolicy">', |
274 | + '<option value="RESTRICTED" ', |
275 | + 'selected="selected">Restricted</option>', |
276 | + '<option value="MODERATED">Moderated</option>', |
277 | + '</select>', |
278 | + '</div>', |
279 | + '</td></tr></table>' |
280 | + ].join(''); |
281 | }, |
282 | |
283 | create_widget: function() { |
284 | @@ -45,6 +66,7 @@ |
285 | responseHeaders: {'Content-Type': 'text/html'}}); |
286 | this.fixture = Y.one('#fixture'); |
287 | this.fixture.appendChild(this.widget.get('container')); |
288 | + this.widget.show(); |
289 | }, |
290 | |
291 | test_library_exists: function () { |
292 | @@ -101,6 +123,37 @@ |
293 | this.widget._save_team_success('', team_data); |
294 | Y.Assert.isTrue(event_publishd); |
295 | Y.Assert.areEqual('test', Y.one('form p').get('text')); |
296 | + }, |
297 | + |
298 | + test_subscriptionpolicy_setup: function() { |
299 | + // The subscription policy choice popup is rendered. |
300 | + this.create_widget(); |
301 | + var subscriptionpolicy_node = |
302 | + Y.one('.subscriptionpolicy-content .value'); |
303 | + Y.Assert.areEqual( |
304 | + 'Restricted', subscriptionpolicy_node.get('text')); |
305 | + var subscriptionpolicy_edit_node = |
306 | + Y.one('.subscriptionpolicy-content a.sprite.edit'); |
307 | + Y.Assert.isNotNull(subscriptionpolicy_edit_node); |
308 | + var legacy_dropdown = Y.one('[id="field.subscriptionpolicy"]'); |
309 | + Y.Assert.isTrue(legacy_dropdown.hasClass('unseen')); |
310 | + }, |
311 | + |
312 | + test_subscriptionpolicy_selection: function() { |
313 | + // The subscriptionpolicy choice popup updates the form. |
314 | + this.create_widget(); |
315 | + var subscriptionpolicy_popup = |
316 | + Y.one('.subscriptionpolicy-content a'); |
317 | + subscriptionpolicy_popup.simulate('click'); |
318 | + var header_text = |
319 | + Y.one('.yui3-ichoicelist-focused .yui3-widget-hd h2') |
320 | + .get('text'); |
321 | + Y.Assert.areEqual('Set subscription policy as', header_text); |
322 | + var subscriptionpolicy_choice = Y.one( |
323 | + '.yui3-ichoicelist-content a[href="#MODERATED"]'); |
324 | + subscriptionpolicy_choice.simulate('click'); |
325 | + var legacy_dropdown = Y.one('[id="field.subscriptionpolicy"]'); |
326 | + Y.Assert.areEqual('MODERATED', legacy_dropdown.get('value')); |
327 | } |
328 | })); |
329 | |
330 | |
331 | === modified file 'lib/lp/bugs/javascript/tests/test_filebug.js' |
332 | --- lib/lp/bugs/javascript/tests/test_filebug.js 2012-06-27 14:05:07 +0000 |
333 | +++ lib/lp/bugs/javascript/tests/test_filebug.js 2012-07-03 07:13:21 +0000 |
334 | @@ -127,9 +127,9 @@ |
335 | // The bugtask status choice popup is rendered. |
336 | test_status_setup: function () { |
337 | Y.lp.bugs.filebug.setup_filebug(true); |
338 | - var status_node = Y.one('#status-content .value'); |
339 | + var status_node = Y.one('.status-content .value'); |
340 | Y.Assert.areEqual('New', status_node.get('text')); |
341 | - var status_edit_node = Y.one('#status-content a.sprite.edit'); |
342 | + var status_edit_node = Y.one('.status-content a.sprite.edit'); |
343 | Y.Assert.isNotNull(status_edit_node); |
344 | var legacy_dropdown = Y.one('[id="field.status"]'); |
345 | Y.Assert.isTrue(legacy_dropdown.hasClass('unseen')); |
346 | @@ -138,10 +138,10 @@ |
347 | // The bugtask importance choice popup is rendered. |
348 | test_importance_setup: function () { |
349 | Y.lp.bugs.filebug.setup_filebug(true); |
350 | - var importance_node = Y.one('#importance-content .value'); |
351 | + var importance_node = Y.one('.importance-content .value'); |
352 | Y.Assert.areEqual('Undecided', importance_node.get('text')); |
353 | var importance_edit_node = |
354 | - Y.one('#importance-content a.sprite.edit'); |
355 | + Y.one('.importance-content a.sprite.edit'); |
356 | Y.Assert.isNotNull(importance_edit_node); |
357 | var legacy_dropdown = Y.one('[id="field.importance"]'); |
358 | Y.Assert.isTrue(legacy_dropdown.hasClass('unseen')); |
359 | @@ -158,7 +158,7 @@ |
360 | // The bugtask status choice popup updates the form. |
361 | test_status_selection: function() { |
362 | Y.lp.bugs.filebug.setup_filebug(true); |
363 | - var status_popup = Y.one('#status-content a'); |
364 | + var status_popup = Y.one('.status-content a'); |
365 | status_popup.simulate('click'); |
366 | var status_choice = Y.one( |
367 | '.yui3-ichoicelist-content a[href="#Incomplete"]'); |
368 | @@ -170,7 +170,7 @@ |
369 | // The bugtask importance choice popup updates the form. |
370 | test_importance_selection: function() { |
371 | Y.lp.bugs.filebug.setup_filebug(true); |
372 | - var status_popup = Y.one('#importance-content a'); |
373 | + var status_popup = Y.one('.importance-content a'); |
374 | status_popup.simulate('click'); |
375 | var status_choice = Y.one( |
376 | '.yui3-ichoicelist-content a[href="#High"]'); |
377 | @@ -183,10 +183,10 @@ |
378 | test_information_type_setup: function () { |
379 | Y.lp.bugs.filebug.setup_filebug(true); |
380 | var information_type_node = |
381 | - Y.one('#information_type-content .value'); |
382 | + Y.one('.information_type-content .value'); |
383 | Y.Assert.areEqual('Public', information_type_node.get('text')); |
384 | var information_type_node_edit_node = |
385 | - Y.one('#information_type-content a.sprite.edit'); |
386 | + Y.one('.information_type-content a.sprite.edit'); |
387 | Y.Assert.isNotNull(information_type_node_edit_node); |
388 | var legacy_field = Y.one('table.radio-button-widget'); |
389 | Y.Assert.isTrue(legacy_field.hasClass('unseen')); |
390 | @@ -195,8 +195,12 @@ |
391 | // The bugtask information_type choice popup updates the form. |
392 | test_information_type_selection: function() { |
393 | Y.lp.bugs.filebug.setup_filebug(true); |
394 | - var information_type_popup = Y.one('#information_type-content a'); |
395 | + var information_type_popup = Y.one('.information_type-content a'); |
396 | information_type_popup.simulate('click'); |
397 | + var header_text = |
398 | + Y.one('.yui3-ichoicelist-focused .yui3-widget-hd h2') |
399 | + .get('text'); |
400 | + Y.Assert.areEqual('Set information type as', header_text); |
401 | var information_type_choice = Y.one( |
402 | '.yui3-ichoicelist-content a[href="#USERDATA"]'); |
403 | information_type_choice.simulate('click'); |
404 | @@ -210,7 +214,7 @@ |
405 | Y.lp.bugs.filebug.setup_filebug(true); |
406 | var banner_hidden = Y.one('.yui3-privacybanner-hidden'); |
407 | Y.Assert.isNotNull(banner_hidden); |
408 | - var information_type_popup = Y.one('#information_type-content a'); |
409 | + var information_type_popup = Y.one('.information_type-content a'); |
410 | information_type_popup.simulate('click'); |
411 | var information_type_choice = Y.one( |
412 | '.yui3-ichoicelist-content a[href="#USERDATA"]'); |
413 | @@ -228,7 +232,7 @@ |
414 | Y.lp.bugs.filebug.setup_filebug(true); |
415 | var banner_hidden = Y.one('.yui3-privacybanner-hidden'); |
416 | Y.Assert.isNotNull(banner_hidden); |
417 | - var information_type_popup = Y.one('#information_type-content a'); |
418 | + var information_type_popup = Y.one('.information_type-content a'); |
419 | information_type_popup.simulate('click'); |
420 | var information_type_choice = Y.one( |
421 | '.yui3-ichoicelist-content a[href="#USERDATA"]'); |
422 | @@ -245,7 +249,7 @@ |
423 | test_select_public_info_type: function () { |
424 | window.LP.cache.bug_private_by_default = true; |
425 | Y.lp.bugs.filebug.setup_filebug(true); |
426 | - var information_type_popup = Y.one('#information_type-content a'); |
427 | + var information_type_popup = Y.one('.information_type-content a'); |
428 | information_type_popup.simulate('click'); |
429 | var information_type_choice = Y.one( |
430 | '.yui3-ichoicelist-content a[href="#USERDATA"]'); |
431 | |
432 | === modified file 'lib/lp/registry/browser/configure.zcml' |
433 | --- lib/lp/registry/browser/configure.zcml 2012-06-25 06:13:53 +0000 |
434 | +++ lib/lp/registry/browser/configure.zcml 2012-07-03 07:13:21 +0000 |
435 | @@ -567,7 +567,7 @@ |
436 | <browser:page |
437 | name="+get-involved" |
438 | for="*" |
439 | - class="lp.registry.browser.pillar.PillarView" |
440 | + class="lp.registry.browser.pillar.PillarInvolvementView" |
441 | permission="zope.Public" |
442 | template="../templates/pillar-involvement-portlet.pt"/> |
443 | <browser:url |
444 | |
445 | === modified file 'lib/lp/registry/browser/distribution.py' |
446 | --- lib/lp/registry/browser/distribution.py 2012-06-14 05:18:22 +0000 |
447 | +++ lib/lp/registry/browser/distribution.py 2012-07-03 07:13:21 +0000 |
448 | @@ -93,6 +93,7 @@ |
449 | from lp.registry.browser.pillar import ( |
450 | PillarBugsMenu, |
451 | PillarNavigationMixin, |
452 | + PillarViewMixin, |
453 | ) |
454 | from lp.registry.interfaces.distribution import ( |
455 | IDerivativeDistribution, |
456 | @@ -648,7 +649,7 @@ |
457 | return self.has_exact_matches |
458 | |
459 | |
460 | -class DistributionView(HasAnnouncementsView, FeedsMixin): |
461 | +class DistributionView(PillarViewMixin, HasAnnouncementsView, FeedsMixin): |
462 | """Default Distribution view class.""" |
463 | |
464 | def initialize(self): |
465 | @@ -666,7 +667,7 @@ |
466 | self.context, IDistribution['owner'], |
467 | format_link(self.context.owner), |
468 | header='Change maintainer', edit_view='+reassign', |
469 | - step_title='Select a new maintainer') |
470 | + step_title='Select a new maintainer', show_create_team=True) |
471 | |
472 | @property |
473 | def driver_widget(self): |
474 | @@ -679,7 +680,7 @@ |
475 | format_link(self.context.driver, empty_value=empty_value), |
476 | header='Change driver', edit_view='+driver', |
477 | null_display_value=empty_value, |
478 | - step_title='Select a new driver') |
479 | + step_title='Select a new driver', show_create_team=True) |
480 | |
481 | @property |
482 | def members_widget(self): |
483 | |
484 | === modified file 'lib/lp/registry/browser/pillar.py' |
485 | --- lib/lp/registry/browser/pillar.py 2012-05-15 08:16:09 +0000 |
486 | +++ lib/lp/registry/browser/pillar.py 2012-07-03 07:13:21 +0000 |
487 | @@ -1,4 +1,4 @@ |
488 | -# Copyright 2009-2010 Canonical Ltd. This software is licensed under the |
489 | +# Copyright 2009-2012 Canonical Ltd. This software is licensed under the |
490 | # GNU Affero General Public License version 3 (see the file LICENSE). |
491 | |
492 | """Common views for objects that implement `IPillar`.""" |
493 | @@ -8,7 +8,8 @@ |
494 | __all__ = [ |
495 | 'InvolvedMenu', |
496 | 'PillarBugsMenu', |
497 | - 'PillarView', |
498 | + 'PillarInvolvementView', |
499 | + 'PillarViewMixin', |
500 | 'PillarNavigationMixin', |
501 | 'PillarPersonSharingView', |
502 | 'PillarSharingView', |
503 | @@ -26,11 +27,15 @@ |
504 | implements, |
505 | Interface, |
506 | ) |
507 | -from zope.schema.vocabulary import getVocabularyRegistry |
508 | +from zope.schema.vocabulary import ( |
509 | + getVocabularyRegistry, |
510 | + SimpleVocabulary, |
511 | + ) |
512 | from zope.security.interfaces import Unauthorized |
513 | from zope.traversing.browser.absoluteurl import absoluteURL |
514 | |
515 | from lp.app.browser.launchpad import iter_view_registrations |
516 | +from lp.app.browser.lazrjs import vocabulary_to_choice_edit_items |
517 | from lp.app.browser.tales import MenuAPI |
518 | from lp.app.browser.vocabulary import vocabulary_filters |
519 | from lp.app.enums import ( |
520 | @@ -47,7 +52,10 @@ |
521 | IDistributionSourcePackage, |
522 | ) |
523 | from lp.registry.interfaces.distroseries import IDistroSeries |
524 | -from lp.registry.interfaces.person import IPersonSet |
525 | +from lp.registry.interfaces.person import ( |
526 | + CLOSED_TEAM_POLICY, |
527 | + IPersonSet, |
528 | + ) |
529 | from lp.registry.interfaces.pillar import IPillar |
530 | from lp.registry.interfaces.projectgroup import IProjectGroup |
531 | from lp.registry.model.pillar import PillarPerson |
532 | @@ -131,15 +139,15 @@ |
533 | enabled=service_uses_launchpad(self.pillar.blueprints_usage)) |
534 | |
535 | |
536 | -class PillarView(LaunchpadView): |
537 | - """A view for any `IPillar`.""" |
538 | +class PillarInvolvementView(LaunchpadView): |
539 | + """A view for any `IPillar` implementing the IInvolved interface.""" |
540 | implements(IInvolved) |
541 | |
542 | configuration_links = [] |
543 | visible_disabled_link_names = [] |
544 | |
545 | def __init__(self, context, request): |
546 | - super(PillarView, self).__init__(context, request) |
547 | + super(PillarInvolvementView, self).__init__(context, request) |
548 | self.official_malone = False |
549 | self.answers_usage = ServiceUsage.UNKNOWN |
550 | self.blueprints_usage = ServiceUsage.UNKNOWN |
551 | @@ -252,6 +260,21 @@ |
552 | return Link('+securitycontact', text, icon='edit') |
553 | |
554 | |
555 | +class PillarViewMixin(): |
556 | + """A mixin for pillar views to populate the json request cache.""" |
557 | + |
558 | + def initialize(self): |
559 | + # Insert close team subscription policy data into the json cache. |
560 | + # This data is used for the maintainer and driver pickers. |
561 | + cache = IJSONRequestCache(self.request) |
562 | + policy_items = [(item.name, item) for item in CLOSED_TEAM_POLICY] |
563 | + team_subscriptionpolicy_data = vocabulary_to_choice_edit_items( |
564 | + SimpleVocabulary.fromItems(policy_items), |
565 | + value_fn=lambda item: item.name) |
566 | + cache.objects['team_subscriptionpolicy_data'] = ( |
567 | + team_subscriptionpolicy_data) |
568 | + |
569 | + |
570 | class PillarSharingView(LaunchpadView): |
571 | |
572 | page_title = "Sharing" |
573 | |
574 | === modified file 'lib/lp/registry/browser/product.py' |
575 | --- lib/lp/registry/browser/product.py 2012-06-21 06:50:10 +0000 |
576 | +++ lib/lp/registry/browser/product.py 2012-07-03 07:13:21 +0000 |
577 | @@ -149,8 +149,9 @@ |
578 | ) |
579 | from lp.registry.browser.pillar import ( |
580 | PillarBugsMenu, |
581 | + PillarInvolvementView, |
582 | PillarNavigationMixin, |
583 | - PillarView, |
584 | + PillarViewMixin, |
585 | ) |
586 | from lp.registry.browser.productseries import get_series_branch_error |
587 | from lp.registry.interfaces.pillar import IPillarNameSet |
588 | @@ -339,7 +340,7 @@ |
589 | return Link('', text, summary) |
590 | |
591 | |
592 | -class ProductInvolvementView(PillarView): |
593 | +class ProductInvolvementView(PillarInvolvementView): |
594 | """Encourage configuration of involvement links for projects.""" |
595 | |
596 | has_involvement = True |
597 | @@ -927,8 +928,8 @@ |
598 | return None |
599 | |
600 | |
601 | -class ProductView(HasAnnouncementsView, SortSeriesMixin, FeedsMixin, |
602 | - ProductDownloadFileMixin): |
603 | +class ProductView(PillarViewMixin, HasAnnouncementsView, SortSeriesMixin, |
604 | + FeedsMixin, ProductDownloadFileMixin): |
605 | |
606 | implements(IProductActionMenu, IEditableContextTitle) |
607 | |
608 | |
609 | === modified file 'lib/lp/registry/browser/productseries.py' |
610 | --- lib/lp/registry/browser/productseries.py 2012-06-19 18:29:44 +0000 |
611 | +++ lib/lp/registry/browser/productseries.py 2012-07-03 07:13:21 +0000 |
612 | @@ -110,7 +110,7 @@ |
613 | ) |
614 | from lp.registry.browser.pillar import ( |
615 | InvolvedMenu, |
616 | - PillarView, |
617 | + PillarInvolvementView, |
618 | ) |
619 | from lp.registry.interfaces.packaging import ( |
620 | IPackaging, |
621 | @@ -236,7 +236,7 @@ |
622 | return self.view.context.product |
623 | |
624 | |
625 | -class ProductSeriesInvolvementView(PillarView): |
626 | +class ProductSeriesInvolvementView(PillarInvolvementView): |
627 | """Encourage configuration of involvement links for project series.""" |
628 | |
629 | implements(IProductSeriesInvolved) |
630 | |
631 | === modified file 'lib/lp/registry/browser/project.py' |
632 | --- lib/lp/registry/browser/project.py 2012-01-04 12:08:24 +0000 |
633 | +++ lib/lp/registry/browser/project.py 2012-07-03 07:13:21 +0000 |
634 | @@ -2,6 +2,7 @@ |
635 | # GNU Affero General Public License version 3 (see the file LICENSE). |
636 | |
637 | """Project-related View Classes""" |
638 | +from lp.registry.browser.pillar import PillarViewMixin |
639 | |
640 | __metaclass__ = type |
641 | |
642 | @@ -356,7 +357,7 @@ |
643 | return Link('+filebug', text, icon='add') |
644 | |
645 | |
646 | -class ProjectView(HasAnnouncementsView, FeedsMixin): |
647 | +class ProjectView(PillarViewMixin, HasAnnouncementsView, FeedsMixin): |
648 | |
649 | implements(IProjectGroupActionMenu) |
650 | |
651 | @@ -367,7 +368,7 @@ |
652 | format_link(self.context.owner, empty_value="Not yet selected"), |
653 | header='Change maintainer', edit_view='+reassign', |
654 | step_title='Select a new maintainer', |
655 | - null_display_value="Not yet selected") |
656 | + null_display_value="Not yet selected", show_create_team=True) |
657 | |
658 | @property |
659 | def driver_widget(self): |
660 | @@ -377,7 +378,7 @@ |
661 | header='Change driver', edit_view='+driver', |
662 | step_title='Select a new driver', |
663 | null_display_value="Not yet selected", |
664 | - help_link="/+help-registry/driver.html") |
665 | + help_link="/+help-registry/driver.html", show_create_team=True) |
666 | |
667 | def initialize(self): |
668 | super(ProjectView, self).initialize() |
669 | |
670 | === modified file 'lib/lp/registry/browser/tests/test_distribution.py' |
671 | --- lib/lp/registry/browser/tests/test_distribution.py 2012-06-14 05:18:22 +0000 |
672 | +++ lib/lp/registry/browser/tests/test_distribution.py 2012-07-03 07:13:21 +0000 |
673 | @@ -12,6 +12,10 @@ |
674 | Not, |
675 | ) |
676 | |
677 | +from zope.schema.vocabulary import SimpleVocabulary |
678 | +from lazr.restful.interfaces import IJSONRequestCache |
679 | +from lp.app.browser.lazrjs import vocabulary_to_choice_edit_items |
680 | +from lp.registry.interfaces.person import CLOSED_TEAM_POLICY |
681 | from lp.registry.interfaces.series import SeriesStatus |
682 | from lp.services.webapp import canonical_url |
683 | from lp.testing import ( |
684 | @@ -76,7 +80,7 @@ |
685 | |
686 | def test_distributionpage_series_list_noadmin(self): |
687 | # A non-admin does see the series list when there is a series. |
688 | - series = self.factory.makeDistroSeries(distribution=self.distro, |
689 | + self.factory.makeDistroSeries(distribution=self.distro, |
690 | status=SeriesStatus.CURRENT) |
691 | login_person(self.simple_user) |
692 | view = create_initialized_view( |
693 | @@ -93,3 +97,26 @@ |
694 | text='Active series and milestones')) |
695 | self.assertThat(view.render(), series_header_match) |
696 | self.assertThat(view.render(), Not(add_series_match)) |
697 | + |
698 | + |
699 | +class TestDistributionView(TestCaseWithFactory): |
700 | + """Tests the DistributionView.""" |
701 | + |
702 | + layer = DatabaseFunctionalLayer |
703 | + |
704 | + def setUp(self): |
705 | + super(TestDistributionView, self).setUp() |
706 | + self.distro = self.factory.makeDistribution( |
707 | + name="distro", displayname=u'distro') |
708 | + |
709 | + def test_view_data_model(self): |
710 | + # The view's json request cache contains the expected data. |
711 | + view = create_initialized_view(self.distro, '+index') |
712 | + cache = IJSONRequestCache(view.request) |
713 | + policy_items = [(item.name, item) for item in CLOSED_TEAM_POLICY] |
714 | + team_subscriptionpolicy_data = vocabulary_to_choice_edit_items( |
715 | + SimpleVocabulary.fromItems(policy_items), |
716 | + value_fn=lambda item: item.name) |
717 | + self.assertContentEqual( |
718 | + team_subscriptionpolicy_data, |
719 | + cache.objects['team_subscriptionpolicy_data']) |
720 | |
721 | === modified file 'lib/lp/registry/browser/tests/test_product.py' |
722 | --- lib/lp/registry/browser/tests/test_product.py 2012-05-25 21:16:11 +0000 |
723 | +++ lib/lp/registry/browser/tests/test_product.py 2012-07-03 07:13:21 +0000 |
724 | @@ -6,8 +6,12 @@ |
725 | __metaclass__ = type |
726 | |
727 | from zope.component import getUtility |
728 | +from zope.schema.vocabulary import SimpleVocabulary |
729 | |
730 | +from lazr.restful.interfaces import IJSONRequestCache |
731 | +from lp.app.browser.lazrjs import vocabulary_to_choice_edit_items |
732 | from lp.app.enums import ServiceUsage |
733 | +from lp.registry.interfaces.person import CLOSED_TEAM_POLICY |
734 | from lp.registry.interfaces.product import ( |
735 | IProductSet, |
736 | License, |
737 | @@ -208,6 +212,18 @@ |
738 | 'fnord-dom-edit-license-approved', |
739 | view.license_approved_widget.content_box_id) |
740 | |
741 | + def test_view_data_model(self): |
742 | + # The view's json request cache contains the expected data. |
743 | + view = create_initialized_view(self.product, '+index') |
744 | + cache = IJSONRequestCache(view.request) |
745 | + policy_items = [(item.name, item) for item in CLOSED_TEAM_POLICY] |
746 | + team_subscriptionpolicy_data = vocabulary_to_choice_edit_items( |
747 | + SimpleVocabulary.fromItems(policy_items), |
748 | + value_fn=lambda item: item.name) |
749 | + self.assertContentEqual( |
750 | + team_subscriptionpolicy_data, |
751 | + cache.objects['team_subscriptionpolicy_data']) |
752 | + |
753 | |
754 | class ProductSetReviewLicensesViewTestCase(TestCaseWithFactory): |
755 | """Tests the ProductSetReviewLicensesView.""" |
756 | |
757 | === modified file 'lib/lp/registry/browser/tests/test_projectgroup.py' |
758 | --- lib/lp/registry/browser/tests/test_projectgroup.py 2012-06-04 16:13:51 +0000 |
759 | +++ lib/lp/registry/browser/tests/test_projectgroup.py 2012-07-03 07:13:21 +0000 |
760 | @@ -8,9 +8,15 @@ |
761 | from fixtures import FakeLogger |
762 | from testtools.matchers import Not |
763 | from zope.component import getUtility |
764 | +from zope.schema.vocabulary import SimpleVocabulary |
765 | from zope.security.interfaces import Unauthorized |
766 | |
767 | -from lp.registry.interfaces.person import IPersonSet |
768 | +from lazr.restful.interfaces import IJSONRequestCache |
769 | +from lp.app.browser.lazrjs import vocabulary_to_choice_edit_items |
770 | +from lp.registry.interfaces.person import ( |
771 | + CLOSED_TEAM_POLICY, |
772 | + IPersonSet, |
773 | + ) |
774 | from lp.services.webapp import canonical_url |
775 | from lp.services.webapp.interfaces import ILaunchBag |
776 | from lp.testing import ( |
777 | @@ -24,6 +30,28 @@ |
778 | from lp.testing.views import create_initialized_view |
779 | |
780 | |
781 | +class TestProjectGroupView(TestCaseWithFactory): |
782 | + """Tests the +index view.""" |
783 | + |
784 | + layer = DatabaseFunctionalLayer |
785 | + |
786 | + def setUp(self): |
787 | + super(TestProjectGroupView, self).setUp() |
788 | + self.project_group = self.factory.makeProject(name='group') |
789 | + |
790 | + def test_view_data_model(self): |
791 | + # The view's json request cache contains the expected data. |
792 | + view = create_initialized_view(self.project_group, '+index') |
793 | + cache = IJSONRequestCache(view.request) |
794 | + policy_items = [(item.name, item) for item in CLOSED_TEAM_POLICY] |
795 | + team_subscriptionpolicy_data = vocabulary_to_choice_edit_items( |
796 | + SimpleVocabulary.fromItems(policy_items), |
797 | + value_fn=lambda item: item.name) |
798 | + self.assertContentEqual( |
799 | + team_subscriptionpolicy_data, |
800 | + cache.objects['team_subscriptionpolicy_data']) |
801 | + |
802 | + |
803 | class TestProjectGroupEditView(TestCaseWithFactory): |
804 | """Tests the edit view.""" |
805 | |
806 | @@ -31,7 +59,7 @@ |
807 | |
808 | def setUp(self): |
809 | super(TestProjectGroupEditView, self).setUp() |
810 | - self.project_group = self.factory.makeProject(name='grupo') |
811 | + self.project_group = self.factory.makeProject(name='group') |
812 | # Use a FakeLogger fixture to prevent Memcached warnings to be |
813 | # printed to stdout while browsing pages. |
814 | self.useFixture(FakeLogger()) |
Thanks Ian. I'm reviewing the code, but leave the question on the select dropdown to others. It seems a bit simple to turn over into the larger UI widget, but there's something to be said for consistant look/feel.
#9
The var is already not attached to the namespace so it's forced private. I wouldn't prefix it with an underscore unless it was attached to an object instance and you wanted to indicate it should be private on that object.
#158 icyedit is a bit hard on the eyes. I generally find that once a name gets past two distinct words, breaking up with _ helps. For instance, I notice you used team_subscripti onpolicy_ data, and think that'd be a good standard to keep with. subscriptionpol icy_XXXXX.
subscriptionpol
#775
Typo: grupo