launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #09502
[Merge] lp:~wallyworld/launchpad/new-team-picker-enhanced-form into lp:launchpad
Ian Booth has proposed merging lp:~wallyworld/launchpad/new-team-picker-enhanced-form into lp:launchpad.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #250955 in Launchpad itself: "easily create team when needed"
https://bugs.launchpad.net/launchpad/+bug/250955
Bug #1019584 in Launchpad itself: "The "information_type" type picker on +filebug has an underscore"
https://bugs.launchpad.net/launchpad/+bug/1019584
For more details, see:
https://code.launchpad.net/~wallyworld/launchpad/new-team-picker-enhanced-form/+merge/113020
== 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 PillarInvolvementView so I renamed it to eliminate any possible confusion.
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://people.canonical.com/~ianb/enhanced-newteam-picker.png
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|Distribution|ProjectGroup]View to ensure the json cache has all the required data.
Add yui tests for the enhanced new team form.
== Lint ==
Checking for conflicts and issues in changed files.
Linting changed files:
lib/lp/app/javascript/choice.js
lib/lp/app/javascript/picker/team.js
lib/lp/app/javascript/picker/tests/test_team.html
lib/lp/app/javascript/picker/tests/test_team.js
lib/lp/archiveuploader/nascentupload.py
lib/lp/bugs/javascript/tests/test_filebug.js
lib/lp/registry/browser/configure.zcml
lib/lp/registry/browser/distribution.py
lib/lp/registry/browser/pillar.py
lib/lp/registry/browser/product.py
lib/lp/registry/browser/productseries.py
lib/lp/registry/browser/project.py
lib/lp/registry/browser/tests/test_distribution.py
lib/lp/registry/browser/tests/test_product.py
lib/lp/registry/browser/tests/test_projectgroup.py
--
https://code.launchpad.net/~wallyworld/launchpad/new-team-picker-enhanced-form/+merge/113020
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~wallyworld/launchpad/new-team-picker-enhanced-form into lp:launchpad.
=== modified file 'lib/lp/app/javascript/choice.js'
--- lib/lp/app/javascript/choice.js 2012-06-27 14:05:07 +0000
+++ lib/lp/app/javascript/choice.js 2012-07-03 01:38:21 +0000
@@ -56,54 +56,65 @@
widget.render();
};
+// The default configuration used for wiring choice popup widgets.
+var _default_popup_choice_config = {
+ container: Y,
+ render_immediately: true,
+ show_description: false,
+ field_title: null
+};
+
/**
* Replace a legacy input widget with a popup choice widget.
* @param legacy_node the YUI node containing the legacy widget.
* @param field_name the Launchpad form field name.
* @param choices the choices for the popup choice widget.
- * @param show_description whether to show the selected value's description.
+ * @param cfg configuration for the wiring action.
* @param get_value_fn getter for the legacy widget's value.
* @param set_value_fn setter for the legacy widget's value.
*/
-var wirePopupChoice = function(legacy_node, field_name, choices,
- show_description, get_value_fn, set_value_fn) {
+var wirePopupChoice = function(legacy_node, field_name, choices, cfg,
+ get_value_fn, set_value_fn) {
var choice_descriptions = {};
Y.Array.forEach(choices, function(item) {
choice_descriptions[item.value] = item.description;
});
var initial_field_value = get_value_fn(legacy_node);
var choice_node = Y.Node.create([
- '<span id="' + field_name + '-content"><span class="value"></span>',
+ '<span class="' + field_name + '-content"><span class="value"></span>',
'<a class="sprite edit editicon action-icon"',
' href="#">Edit</a></span>'
].join(''));
- if (show_description) {
+ if (cfg.show_description) {
choice_node.append(Y.Node.create('<div class="formHelp"></div>'));
}
-
legacy_node.insertBefore(choice_node, legacy_node);
- if (show_description) {
+ if (cfg.show_description) {
choice_node.one('.formHelp')
.set('text', choice_descriptions[initial_field_value]);
}
legacy_node.addClass('unseen');
- var field_content = Y.one('#' + field_name + '-content');
-
+ if (!Y.Lang.isValue(cfg.field_title)) {
+ cfg.field_title = field_name.replace('_', ' ');
+ }
var choice_edit = new Y.ChoiceSource({
- contentBox: field_content,
+ contentBox: choice_node,
value: initial_field_value,
- title: 'Set ' + field_name + " as",
+ title: 'Set ' + cfg.field_title + " as",
items: choices,
- elementToFlash: field_content
+ elementToFlash: choice_node,
+ zIndex: 1050
});
- choice_edit.render();
+ if (cfg.render_immediately) {
+ choice_edit.render();
+ }
var update_selected_value_css = function(selected_value) {
Y.Array.each(choices, function(item) {
if (item.value === selected_value) {
- field_content.addClass(item.css_class);
+ choice_node.addClass(item.css_class);
} else {
- field_content.removeClass(item.css_class);
+ choice_node.removeClass(item.css_class);
}
});
};
@@ -112,21 +123,23 @@
var selected_value = choice_edit.get('value');
update_selected_value_css(selected_value);
set_value_fn(legacy_node, selected_value);
- if (show_description) {
+ if (cfg.show_description) {
choice_node.one('.formHelp')
.set('text', choice_descriptions[selected_value]);
}
});
+ return choice_edit;
};
/**
* Replace a drop down combo box with a popup choice selection widget.
* @param field_name
* @param choices
- * @param show_description
+ * @param cfg
*/
-namespace.addPopupChoice = function(field_name, choices, show_description) {
- var legacy_node = Y.one('[id="field.' + field_name + '"]');
+namespace.addPopupChoice = function(field_name, choices, cfg) {
+ cfg = Y.merge(_default_popup_choice_config, cfg);
+ var legacy_node = cfg.container.one('[id="field.' + field_name + '"]');
if (!Y.Lang.isValue(legacy_node)) {
return;
}
@@ -136,19 +149,19 @@
var set_fn = function(node, value) {
node.set('value', value);
};
- wirePopupChoice(
- legacy_node, field_name, choices, show_description, get_fn, set_fn);
+ return wirePopupChoice(
+ legacy_node, field_name, choices, cfg, get_fn, set_fn);
};
/**
* Replace a radio button group with a popup choice selection widget.
* @param field_name
* @param choices
- * @param show_description
+ * @param cfg
*/
-namespace.addPopupChoiceForRadioButtons = function(field_name, choices,
- show_description) {
- var legacy_node = Y.one('[name="field.' + field_name + '"]')
+namespace.addPopupChoiceForRadioButtons = function(field_name, choices, cfg) {
+ cfg = Y.merge(_default_popup_choice_config, cfg);
+ var legacy_node = cfg.container.one('[name="field.' + field_name + '"]')
.ancestor('table.radio-button-widget');
if (!Y.Lang.isValue(legacy_node)) {
return;
@@ -172,8 +185,8 @@
}
});
};
- wirePopupChoice(
- legacy_node, field_name, choices, show_description, get_fn, set_fn);
+ return wirePopupChoice(
+ legacy_node, field_name, choices, cfg, get_fn, set_fn);
};
}, "0.1", {"requires": ["lazr.choiceedit", "lp.client.plugins",
=== modified file 'lib/lp/app/javascript/picker/team.js'
--- lib/lp/app/javascript/picker/team.js 2012-07-02 04:16:19 +0000
+++ lib/lp/app/javascript/picker/team.js 2012-07-03 01:38:21 +0000
@@ -106,6 +106,12 @@
e.halt();
this.fire(ns.CANCEL_TEAM);
}, this);
+ this.subscriptionpolicyedit = Y.lp.app.choice.addPopupChoice(
+ 'subscriptionpolicy', LP.cache.team_subscriptionpolicy_data, {
+ container: container,
+ render_immediately: false,
+ field_title: 'subscription policy'
+ });
container.one('.extra-form-buttons').removeClass('hidden');
},
@@ -114,6 +120,7 @@
if (form_elements.size() > 0) {
form_elements.item(0).focus();
}
+ this.subscriptionpolicyedit.render();
},
hide: function() {
@@ -214,5 +221,5 @@
});
-}, "0.1", {"requires": ["base", "node"]});
+}, "0.1", {"requires": ["base", "node", "lp.app.choice"]});
=== modified file 'lib/lp/app/javascript/picker/tests/test_team.html'
--- lib/lp/app/javascript/picker/tests/test_team.html 2012-06-26 09:02:51 +0000
+++ lib/lp/app/javascript/picker/tests/test_team.html 2012-07-03 01:38:21 +0000
@@ -26,12 +26,34 @@
<!-- Dependencies -->
<script type="text/javascript"
+ src="../../../../../../build/js/lp/app/choice.js"></script>
+ <script type="text/javascript"
src="../../../../../../build/js/lp/app/client.js"></script>
<script type="text/javascript"
+ src="../../../../../../build/js/lp/app/errors.js"></script>
+ <script type="text/javascript"
src="../../../../../../build/js/lp/app/lp.js"></script>
<script type="text/javascript"
src="../../../../../../build/js/lp/app/lazr/lazr.js"></script>
<script type="text/javascript"
+ src="../../../../../../build/js/lp/app/choiceedit/choiceedit.js"></script>
+ <script type="text/javascript"
+ src="../../../../../../build/js/lp/app/overlay/overlay.js"></script>
+ <script type="text/javascript"
+ src="../../../../../../build/js/lp/app/anim/anim.js"></script>
+ <script type="text/javascript"
+ src="../../../../../../build/js/lp/app/effects/effects.js"></script>
+ <script type="text/javascript"
+ src="../../../../../../build/js/lp/app/expander.js"></script>
+ <script type="text/javascript"
+ src="../../../../../../build/js/lp/app/extras/extras.js"></script>
+ <script type="text/javascript"
+ src="../../../../../../build/js/lp/app/formoverlay/formoverlay.js"></script>
+ <script type="text/javascript"
+ src="../../../../../../build/js/lp/app/formwidgets/resizing_textarea.js"></script>
+ <script type="text/javascript"
+ src="../../../../../../build/js/lp/app/inlineedit/editor.js"></script>
+ <script type="text/javascript"
src="../../../../../../build/js/lp/app/testing/mockio.js"></script>
<!-- The module under test. -->
=== modified file 'lib/lp/app/javascript/picker/tests/test_team.js'
--- lib/lp/app/javascript/picker/tests/test_team.js 2012-07-02 04:16:19 +0000
+++ lib/lp/app/javascript/picker/tests/test_team.js 2012-07-03 01:38:21 +0000
@@ -12,9 +12,19 @@
setUp: function() {
+ window.LP = {
+ links: {},
+ cache: {
+ team_subscriptionpolicy_data: [
+ {name: 'Moderated', value: 'MODERATED'},
+ {name: 'Restricted', value: 'RESTRICTED'}
+ ]
+ }
+ };
},
tearDown: function() {
+ delete window.LP;
delete this.mockio;
if (this.fixture !== undefined) {
this.fixture.empty(true);
@@ -26,10 +36,21 @@
},
_simple_team_form: function() {
- return '<table><tr><td>' +
- '<input id="field.name" name="field.name"/>' +
- '<input id="field.displayname" ' +
- 'name="field.displayname"/></td></tr></table>';
+ return [
+ '<table><tr><td>',
+ '<input id="field.name" name="field.name"/>',
+ '<input id="field.displayname" ',
+ 'name="field.displayname"/>',
+ '<div class="value">',
+ '<select size="1" name="field.subscriptionpolicy" ',
+ 'id="field.subscriptionpolicy">',
+ '<option value="RESTRICTED" ',
+ 'selected="selected">Restricted</option>',
+ '<option value="MODERATED">Moderated</option>',
+ '</select>',
+ '</div>',
+ '</td></tr></table>'
+ ].join('');
},
create_widget: function() {
@@ -45,6 +66,7 @@
responseHeaders: {'Content-Type': 'text/html'}});
this.fixture = Y.one('#fixture');
this.fixture.appendChild(this.widget.get('container'));
+ this.widget.show();
},
test_library_exists: function () {
@@ -101,6 +123,37 @@
this.widget._save_team_success('', team_data);
Y.Assert.isTrue(event_publishd);
Y.Assert.areEqual('test', Y.one('form p').get('text'));
+ },
+
+ test_subscriptionpolicy_setup: function() {
+ // The subscription policy choice popup is rendered.
+ this.create_widget();
+ var subscriptionpolicy_node =
+ Y.one('.subscriptionpolicy-content .value');
+ Y.Assert.areEqual(
+ 'Restricted', subscriptionpolicy_node.get('text'));
+ var subscriptionpolicy_edit_node =
+ Y.one('.subscriptionpolicy-content a.sprite.edit');
+ Y.Assert.isNotNull(subscriptionpolicy_edit_node);
+ var legacy_dropdown = Y.one('[id="field.subscriptionpolicy"]');
+ Y.Assert.isTrue(legacy_dropdown.hasClass('unseen'));
+ },
+
+ test_subscriptionpolicy_selection: function() {
+ // The subscriptionpolicy choice popup updates the form.
+ this.create_widget();
+ var subscriptionpolicy_popup =
+ Y.one('.subscriptionpolicy-content a');
+ subscriptionpolicy_popup.simulate('click');
+ var header_text =
+ Y.one('.yui3-ichoicelist-focused .yui3-widget-hd h2')
+ .get('text');
+ Y.Assert.areEqual('Set subscription policy as', header_text);
+ var subscriptionpolicy_choice = Y.one(
+ '.yui3-ichoicelist-content a[href="#MODERATED"]');
+ subscriptionpolicy_choice.simulate('click');
+ var legacy_dropdown = Y.one('[id="field.subscriptionpolicy"]');
+ Y.Assert.areEqual('MODERATED', legacy_dropdown.get('value'));
}
}));
=== modified file 'lib/lp/bugs/javascript/tests/test_filebug.js'
--- lib/lp/bugs/javascript/tests/test_filebug.js 2012-06-27 14:05:07 +0000
+++ lib/lp/bugs/javascript/tests/test_filebug.js 2012-07-03 01:38:21 +0000
@@ -127,9 +127,9 @@
// The bugtask status choice popup is rendered.
test_status_setup: function () {
Y.lp.bugs.filebug.setup_filebug(true);
- var status_node = Y.one('#status-content .value');
+ var status_node = Y.one('.status-content .value');
Y.Assert.areEqual('New', status_node.get('text'));
- var status_edit_node = Y.one('#status-content a.sprite.edit');
+ var status_edit_node = Y.one('.status-content a.sprite.edit');
Y.Assert.isNotNull(status_edit_node);
var legacy_dropdown = Y.one('[id="field.status"]');
Y.Assert.isTrue(legacy_dropdown.hasClass('unseen'));
@@ -138,10 +138,10 @@
// The bugtask importance choice popup is rendered.
test_importance_setup: function () {
Y.lp.bugs.filebug.setup_filebug(true);
- var importance_node = Y.one('#importance-content .value');
+ var importance_node = Y.one('.importance-content .value');
Y.Assert.areEqual('Undecided', importance_node.get('text'));
var importance_edit_node =
- Y.one('#importance-content a.sprite.edit');
+ Y.one('.importance-content a.sprite.edit');
Y.Assert.isNotNull(importance_edit_node);
var legacy_dropdown = Y.one('[id="field.importance"]');
Y.Assert.isTrue(legacy_dropdown.hasClass('unseen'));
@@ -158,7 +158,7 @@
// The bugtask status choice popup updates the form.
test_status_selection: function() {
Y.lp.bugs.filebug.setup_filebug(true);
- var status_popup = Y.one('#status-content a');
+ var status_popup = Y.one('.status-content a');
status_popup.simulate('click');
var status_choice = Y.one(
'.yui3-ichoicelist-content a[href="#Incomplete"]');
@@ -170,7 +170,7 @@
// The bugtask importance choice popup updates the form.
test_importance_selection: function() {
Y.lp.bugs.filebug.setup_filebug(true);
- var status_popup = Y.one('#importance-content a');
+ var status_popup = Y.one('.importance-content a');
status_popup.simulate('click');
var status_choice = Y.one(
'.yui3-ichoicelist-content a[href="#High"]');
@@ -183,10 +183,10 @@
test_information_type_setup: function () {
Y.lp.bugs.filebug.setup_filebug(true);
var information_type_node =
- Y.one('#information_type-content .value');
+ Y.one('.information_type-content .value');
Y.Assert.areEqual('Public', information_type_node.get('text'));
var information_type_node_edit_node =
- Y.one('#information_type-content a.sprite.edit');
+ Y.one('.information_type-content a.sprite.edit');
Y.Assert.isNotNull(information_type_node_edit_node);
var legacy_field = Y.one('table.radio-button-widget');
Y.Assert.isTrue(legacy_field.hasClass('unseen'));
@@ -195,8 +195,12 @@
// The bugtask information_type choice popup updates the form.
test_information_type_selection: function() {
Y.lp.bugs.filebug.setup_filebug(true);
- var information_type_popup = Y.one('#information_type-content a');
+ var information_type_popup = Y.one('.information_type-content a');
information_type_popup.simulate('click');
+ var header_text =
+ Y.one('.yui3-ichoicelist-focused .yui3-widget-hd h2')
+ .get('text');
+ Y.Assert.areEqual('Set information type as', header_text);
var information_type_choice = Y.one(
'.yui3-ichoicelist-content a[href="#USERDATA"]');
information_type_choice.simulate('click');
@@ -210,7 +214,7 @@
Y.lp.bugs.filebug.setup_filebug(true);
var banner_hidden = Y.one('.yui3-privacybanner-hidden');
Y.Assert.isNotNull(banner_hidden);
- var information_type_popup = Y.one('#information_type-content a');
+ var information_type_popup = Y.one('.information_type-content a');
information_type_popup.simulate('click');
var information_type_choice = Y.one(
'.yui3-ichoicelist-content a[href="#USERDATA"]');
@@ -228,7 +232,7 @@
Y.lp.bugs.filebug.setup_filebug(true);
var banner_hidden = Y.one('.yui3-privacybanner-hidden');
Y.Assert.isNotNull(banner_hidden);
- var information_type_popup = Y.one('#information_type-content a');
+ var information_type_popup = Y.one('.information_type-content a');
information_type_popup.simulate('click');
var information_type_choice = Y.one(
'.yui3-ichoicelist-content a[href="#USERDATA"]');
@@ -245,7 +249,7 @@
test_select_public_info_type: function () {
window.LP.cache.bug_private_by_default = true;
Y.lp.bugs.filebug.setup_filebug(true);
- var information_type_popup = Y.one('#information_type-content a');
+ var information_type_popup = Y.one('.information_type-content a');
information_type_popup.simulate('click');
var information_type_choice = Y.one(
'.yui3-ichoicelist-content a[href="#USERDATA"]');
=== modified file 'lib/lp/registry/browser/configure.zcml'
--- lib/lp/registry/browser/configure.zcml 2012-06-25 06:13:53 +0000
+++ lib/lp/registry/browser/configure.zcml 2012-07-03 01:38:21 +0000
@@ -567,7 +567,7 @@
<browser:page
name="+get-involved"
for="*"
- class="lp.registry.browser.pillar.PillarView"
+ class="lp.registry.browser.pillar.PillarInvolvementView"
permission="zope.Public"
template="../templates/pillar-involvement-portlet.pt"/>
<browser:url
=== modified file 'lib/lp/registry/browser/distribution.py'
--- lib/lp/registry/browser/distribution.py 2012-06-14 05:18:22 +0000
+++ lib/lp/registry/browser/distribution.py 2012-07-03 01:38:21 +0000
@@ -93,6 +93,7 @@
from lp.registry.browser.pillar import (
PillarBugsMenu,
PillarNavigationMixin,
+ PillarViewMixin,
)
from lp.registry.interfaces.distribution import (
IDerivativeDistribution,
@@ -648,7 +649,7 @@
return self.has_exact_matches
-class DistributionView(HasAnnouncementsView, FeedsMixin):
+class DistributionView(PillarViewMixin, HasAnnouncementsView, FeedsMixin):
"""Default Distribution view class."""
def initialize(self):
@@ -666,7 +667,7 @@
self.context, IDistribution['owner'],
format_link(self.context.owner),
header='Change maintainer', edit_view='+reassign',
- step_title='Select a new maintainer')
+ step_title='Select a new maintainer', show_create_team=True)
@property
def driver_widget(self):
@@ -679,7 +680,7 @@
format_link(self.context.driver, empty_value=empty_value),
header='Change driver', edit_view='+driver',
null_display_value=empty_value,
- step_title='Select a new driver')
+ step_title='Select a new driver', show_create_team=True)
@property
def members_widget(self):
=== modified file 'lib/lp/registry/browser/pillar.py'
--- lib/lp/registry/browser/pillar.py 2012-05-15 08:16:09 +0000
+++ lib/lp/registry/browser/pillar.py 2012-07-03 01:38:21 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Common views for objects that implement `IPillar`."""
@@ -8,7 +8,8 @@
__all__ = [
'InvolvedMenu',
'PillarBugsMenu',
- 'PillarView',
+ 'PillarInvolvementView',
+ 'PillarViewMixin',
'PillarNavigationMixin',
'PillarPersonSharingView',
'PillarSharingView',
@@ -26,11 +27,15 @@
implements,
Interface,
)
-from zope.schema.vocabulary import getVocabularyRegistry
+from zope.schema.vocabulary import (
+ getVocabularyRegistry,
+ SimpleVocabulary,
+ )
from zope.security.interfaces import Unauthorized
from zope.traversing.browser.absoluteurl import absoluteURL
from lp.app.browser.launchpad import iter_view_registrations
+from lp.app.browser.lazrjs import vocabulary_to_choice_edit_items
from lp.app.browser.tales import MenuAPI
from lp.app.browser.vocabulary import vocabulary_filters
from lp.app.enums import (
@@ -47,7 +52,10 @@
IDistributionSourcePackage,
)
from lp.registry.interfaces.distroseries import IDistroSeries
-from lp.registry.interfaces.person import IPersonSet
+from lp.registry.interfaces.person import (
+ CLOSED_TEAM_POLICY,
+ IPersonSet,
+ )
from lp.registry.interfaces.pillar import IPillar
from lp.registry.interfaces.projectgroup import IProjectGroup
from lp.registry.model.pillar import PillarPerson
@@ -131,15 +139,15 @@
enabled=service_uses_launchpad(self.pillar.blueprints_usage))
-class PillarView(LaunchpadView):
- """A view for any `IPillar`."""
+class PillarInvolvementView(LaunchpadView):
+ """A view for any `IPillar` implementing the IInvolved interface."""
implements(IInvolved)
configuration_links = []
visible_disabled_link_names = []
def __init__(self, context, request):
- super(PillarView, self).__init__(context, request)
+ super(PillarInvolvementView, self).__init__(context, request)
self.official_malone = False
self.answers_usage = ServiceUsage.UNKNOWN
self.blueprints_usage = ServiceUsage.UNKNOWN
@@ -252,6 +260,21 @@
return Link('+securitycontact', text, icon='edit')
+class PillarViewMixin():
+ """A mixin for pillar views to populate the json request cache."""
+
+ def initialize(self):
+ # Insert close team subscription policy data into the json cache.
+ # This data is used for the maintainer and driver pickers.
+ cache = IJSONRequestCache(self.request)
+ policy_items = [(item.name, item) for item in CLOSED_TEAM_POLICY]
+ team_subscriptionpolicy_data = vocabulary_to_choice_edit_items(
+ SimpleVocabulary.fromItems(policy_items),
+ value_fn=lambda item: item.name)
+ cache.objects['team_subscriptionpolicy_data'] = (
+ team_subscriptionpolicy_data)
+
+
class PillarSharingView(LaunchpadView):
page_title = "Sharing"
=== modified file 'lib/lp/registry/browser/product.py'
--- lib/lp/registry/browser/product.py 2012-06-21 06:50:10 +0000
+++ lib/lp/registry/browser/product.py 2012-07-03 01:38:21 +0000
@@ -149,8 +149,9 @@
)
from lp.registry.browser.pillar import (
PillarBugsMenu,
+ PillarInvolvementView,
PillarNavigationMixin,
- PillarView,
+ PillarViewMixin,
)
from lp.registry.browser.productseries import get_series_branch_error
from lp.registry.interfaces.pillar import IPillarNameSet
@@ -339,7 +340,7 @@
return Link('', text, summary)
-class ProductInvolvementView(PillarView):
+class ProductInvolvementView(PillarInvolvementView):
"""Encourage configuration of involvement links for projects."""
has_involvement = True
@@ -927,8 +928,8 @@
return None
-class ProductView(HasAnnouncementsView, SortSeriesMixin, FeedsMixin,
- ProductDownloadFileMixin):
+class ProductView(PillarViewMixin, HasAnnouncementsView, SortSeriesMixin,
+ FeedsMixin, ProductDownloadFileMixin):
implements(IProductActionMenu, IEditableContextTitle)
=== modified file 'lib/lp/registry/browser/productseries.py'
--- lib/lp/registry/browser/productseries.py 2012-06-19 18:29:44 +0000
+++ lib/lp/registry/browser/productseries.py 2012-07-03 01:38:21 +0000
@@ -110,7 +110,7 @@
)
from lp.registry.browser.pillar import (
InvolvedMenu,
- PillarView,
+ PillarInvolvementView,
)
from lp.registry.interfaces.packaging import (
IPackaging,
@@ -236,7 +236,7 @@
return self.view.context.product
-class ProductSeriesInvolvementView(PillarView):
+class ProductSeriesInvolvementView(PillarInvolvementView):
"""Encourage configuration of involvement links for project series."""
implements(IProductSeriesInvolved)
=== modified file 'lib/lp/registry/browser/project.py'
--- lib/lp/registry/browser/project.py 2012-01-04 12:08:24 +0000
+++ lib/lp/registry/browser/project.py 2012-07-03 01:38:21 +0000
@@ -2,6 +2,7 @@
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Project-related View Classes"""
+from lp.registry.browser.pillar import PillarViewMixin
__metaclass__ = type
@@ -356,7 +357,7 @@
return Link('+filebug', text, icon='add')
-class ProjectView(HasAnnouncementsView, FeedsMixin):
+class ProjectView(PillarViewMixin, HasAnnouncementsView, FeedsMixin):
implements(IProjectGroupActionMenu)
@@ -367,7 +368,7 @@
format_link(self.context.owner, empty_value="Not yet selected"),
header='Change maintainer', edit_view='+reassign',
step_title='Select a new maintainer',
- null_display_value="Not yet selected")
+ null_display_value="Not yet selected", show_create_team=True)
@property
def driver_widget(self):
@@ -377,7 +378,7 @@
header='Change driver', edit_view='+driver',
step_title='Select a new driver',
null_display_value="Not yet selected",
- help_link="/+help-registry/driver.html")
+ help_link="/+help-registry/driver.html", show_create_team=True)
def initialize(self):
super(ProjectView, self).initialize()
=== modified file 'lib/lp/registry/browser/tests/test_distribution.py'
--- lib/lp/registry/browser/tests/test_distribution.py 2012-06-14 05:18:22 +0000
+++ lib/lp/registry/browser/tests/test_distribution.py 2012-07-03 01:38:21 +0000
@@ -12,6 +12,10 @@
Not,
)
+from zope.schema.vocabulary import SimpleVocabulary
+from lazr.restful.interfaces import IJSONRequestCache
+from lp.app.browser.lazrjs import vocabulary_to_choice_edit_items
+from lp.registry.interfaces.person import CLOSED_TEAM_POLICY
from lp.registry.interfaces.series import SeriesStatus
from lp.services.webapp import canonical_url
from lp.testing import (
@@ -76,7 +80,7 @@
def test_distributionpage_series_list_noadmin(self):
# A non-admin does see the series list when there is a series.
- series = self.factory.makeDistroSeries(distribution=self.distro,
+ self.factory.makeDistroSeries(distribution=self.distro,
status=SeriesStatus.CURRENT)
login_person(self.simple_user)
view = create_initialized_view(
@@ -93,3 +97,26 @@
text='Active series and milestones'))
self.assertThat(view.render(), series_header_match)
self.assertThat(view.render(), Not(add_series_match))
+
+
+class TestDistributionView(TestCaseWithFactory):
+ """Tests the DistributionView."""
+
+ layer = DatabaseFunctionalLayer
+
+ def setUp(self):
+ super(TestDistributionView, self).setUp()
+ self.distro = self.factory.makeDistribution(
+ name="distro", displayname=u'distro')
+
+ def test_view_data_model(self):
+ # The view's json request cache contains the expected data.
+ view = create_initialized_view(self.distro, '+index')
+ cache = IJSONRequestCache(view.request)
+ policy_items = [(item.name, item) for item in CLOSED_TEAM_POLICY]
+ team_subscriptionpolicy_data = vocabulary_to_choice_edit_items(
+ SimpleVocabulary.fromItems(policy_items),
+ value_fn=lambda item: item.name)
+ self.assertContentEqual(
+ team_subscriptionpolicy_data,
+ cache.objects['team_subscriptionpolicy_data'])
=== modified file 'lib/lp/registry/browser/tests/test_product.py'
--- lib/lp/registry/browser/tests/test_product.py 2012-05-25 21:16:11 +0000
+++ lib/lp/registry/browser/tests/test_product.py 2012-07-03 01:38:21 +0000
@@ -6,8 +6,12 @@
__metaclass__ = type
from zope.component import getUtility
+from zope.schema.vocabulary import SimpleVocabulary
+from lazr.restful.interfaces import IJSONRequestCache
+from lp.app.browser.lazrjs import vocabulary_to_choice_edit_items
from lp.app.enums import ServiceUsage
+from lp.registry.interfaces.person import CLOSED_TEAM_POLICY
from lp.registry.interfaces.product import (
IProductSet,
License,
@@ -208,6 +212,18 @@
'fnord-dom-edit-license-approved',
view.license_approved_widget.content_box_id)
+ def test_view_data_model(self):
+ # The view's json request cache contains the expected data.
+ view = create_initialized_view(self.product, '+index')
+ cache = IJSONRequestCache(view.request)
+ policy_items = [(item.name, item) for item in CLOSED_TEAM_POLICY]
+ team_subscriptionpolicy_data = vocabulary_to_choice_edit_items(
+ SimpleVocabulary.fromItems(policy_items),
+ value_fn=lambda item: item.name)
+ self.assertContentEqual(
+ team_subscriptionpolicy_data,
+ cache.objects['team_subscriptionpolicy_data'])
+
class ProductSetReviewLicensesViewTestCase(TestCaseWithFactory):
"""Tests the ProductSetReviewLicensesView."""
=== modified file 'lib/lp/registry/browser/tests/test_projectgroup.py'
--- lib/lp/registry/browser/tests/test_projectgroup.py 2012-06-04 16:13:51 +0000
+++ lib/lp/registry/browser/tests/test_projectgroup.py 2012-07-03 01:38:21 +0000
@@ -8,9 +8,15 @@
from fixtures import FakeLogger
from testtools.matchers import Not
from zope.component import getUtility
+from zope.schema.vocabulary import SimpleVocabulary
from zope.security.interfaces import Unauthorized
-from lp.registry.interfaces.person import IPersonSet
+from lazr.restful.interfaces import IJSONRequestCache
+from lp.app.browser.lazrjs import vocabulary_to_choice_edit_items
+from lp.registry.interfaces.person import (
+ CLOSED_TEAM_POLICY,
+ IPersonSet,
+ )
from lp.services.webapp import canonical_url
from lp.services.webapp.interfaces import ILaunchBag
from lp.testing import (
@@ -24,6 +30,28 @@
from lp.testing.views import create_initialized_view
+class TestProjectGroupView(TestCaseWithFactory):
+ """Tests the +index view."""
+
+ layer = DatabaseFunctionalLayer
+
+ def setUp(self):
+ super(TestProjectGroupView, self).setUp()
+ self.project_group = self.factory.makeProject(name='grupo')
+
+ def test_view_data_model(self):
+ # The view's json request cache contains the expected data.
+ view = create_initialized_view(self.project_group, '+index')
+ cache = IJSONRequestCache(view.request)
+ policy_items = [(item.name, item) for item in CLOSED_TEAM_POLICY]
+ team_subscriptionpolicy_data = vocabulary_to_choice_edit_items(
+ SimpleVocabulary.fromItems(policy_items),
+ value_fn=lambda item: item.name)
+ self.assertContentEqual(
+ team_subscriptionpolicy_data,
+ cache.objects['team_subscriptionpolicy_data'])
+
+
class TestProjectGroupEditView(TestCaseWithFactory):
"""Tests the edit view."""
Follow ups