Merge lp:~wallyworld/launchpad/branch-infotype-portlet-1040999 into lp:launchpad

Proposed by Ian Booth
Status: Merged
Approved by: Ian Booth
Approved revision: no longer in the source branch.
Merged at revision: 15875
Proposed branch: lp:~wallyworld/launchpad/branch-infotype-portlet-1040999
Merge into: lp:launchpad
Diff against target: 348 lines (+95/-59)
5 files modified
lib/lp/code/browser/branch.py (+31/-13)
lib/lp/code/browser/configure.zcml (+7/-0)
lib/lp/code/browser/tests/test_branch.py (+45/-40)
lib/lp/code/stories/branches/xx-branch-edit-privacy.txt (+4/-4)
lib/lp/code/templates/branch-portlet-privacy.pt (+8/-2)
To merge this branch: bzr merge lp:~wallyworld/launchpad/branch-infotype-portlet-1040999
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+121404@code.launchpad.net

Commit message

Allow branch's information type to be changed from the privacy portlet; always allow branch to be marked as security related.

Description of the change

== Implementation ==

This branch does 2 things:

1. add an edit link to the branch privacy portlet to allow information type to be edited

This branch does not wire up any javascript (that will be done next branch). So the user clicks on the edit link and is taken to a new edit page where they can edit the information type. ie the same non js infrastructure as used elsewhere for lifecycle status etc. The view used to process the information type change does have the ajax processing in place so the next branch will be only javascript.

2. change the algorithm used by getInformationTypesToShow() to determine the allowed information types

Previously, a branch could not be made private/public security unless there were such bugs linked. Now, a branch can always be marked as security. The hooks for checking linked bugs have been left in place though because even though not used right now, very soon user data branches will only be allowed when there are user data linked bugs.

== Tests ==

Add test to TestBranchEditView to check the XHR updating of information type work correctly.

Update tests in TestBranchEditViewInformationTypes to reflect new getInformationTypesToShow algorithm.

Update TestBranchPrivacyPortlet to check for edit link.

== Lint ==

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/code/browser/branch.py
  lib/lp/code/browser/configure.zcml
  lib/lp/code/browser/tests/test_branch.py
  lib/lp/code/templates/branch-portlet-privacy.pt

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) wrote :

16 + @enabled_with_permission('launchpad.Edit')
17 + def visibility(self):
18 + """Return the 'Set information type' Link."""
19 + text = 'Change information type'
20 + return Link('+edit-information-type', text)

Should this be information_type instead of visibility?

55 InformationType.PUBLICSECURITY,
56 InformationType.PRIVATESECURITY,
57 )

Could you fix these to be in the traditional order (between PUBLIC and USERDATA)?

129 + class="lp.code.browser.branch.BranchEditInformationTypeView"
130 + permission="launchpad.Edit"

This should probably be launchpad.Moderate. Curtis added it last week to let project owners change the information type too.

review: Approve (code)
Revision history for this message
Ian Booth (wallyworld) wrote :

Thanks!

> 16 + @enabled_with_permission('launchpad.Edit')
> 17 + def visibility(self):
> 18 + """Return the 'Set information type' Link."""
> 19 + text = 'Change information type'
> 20 + return Link('+edit-information-type', text)
>
> Should this be information_type instead of visibility?
>

I considered it but bugs uses 'visibility' for exactly the same thing and I wanted to be consistent.
How strongly do you feel about changing it?

Revision history for this message
William Grant (wgrant) wrote :

On 28/08/12 13:39, Ian Booth wrote:
> Thanks!
>
>> 16 + @enabled_with_permission('launchpad.Edit')
>> 17 + def visibility(self):
>> 18 + """Return the 'Set information type' Link."""
>> 19 + text = 'Change information type'
>> 20 + return Link('+edit-information-type', text)
>>
>> Should this be information_type instead of visibility?
>>
>
> I considered it but bugs uses 'visibility' for exactly the same thing and I wanted to be consistent.
> How strongly do you feel about changing it?

Not strongly at all, but Bugs probably isn't good precedent, as the name
derives from the old hybrid privacy/security portlet.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/code/browser/branch.py'
--- lib/lp/code/browser/branch.py 2012-08-16 05:55:48 +0000
+++ lib/lp/code/browser/branch.py 2012-08-29 04:50:28 +0000
@@ -308,13 +308,19 @@
308 'add_subscriber', 'browse_revisions', 'create_recipe', 'link_bug',308 'add_subscriber', 'browse_revisions', 'create_recipe', 'link_bug',
309 'link_blueprint', 'register_merge', 'source', 'subscription',309 'link_blueprint', 'register_merge', 'source', 'subscription',
310 'edit_status', 'edit_import', 'upgrade_branch', 'view_recipes',310 'edit_status', 'edit_import', 'upgrade_branch', 'view_recipes',
311 'create_queue']311 'create_queue', 'visibility']
312312
313 @enabled_with_permission('launchpad.Edit')313 @enabled_with_permission('launchpad.Edit')
314 def edit_status(self):314 def edit_status(self):
315 text = 'Change branch status'315 text = 'Change branch status'
316 return Link('+edit-status', text, icon='edit')316 return Link('+edit-status', text, icon='edit')
317317
318 @enabled_with_permission('launchpad.Moderate')
319 def visibility(self):
320 """Return the 'Set information type' Link."""
321 text = 'Change information type'
322 return Link('+edit-information-type', text)
323
318 def browse_revisions(self):324 def browse_revisions(self):
319 """Return a link to the branch's revisions on codebrowse."""325 """Return a link to the branch's revisions on codebrowse."""
320 text = 'All revisions'326 text = 'All revisions'
@@ -430,8 +436,8 @@
430 return branch.url436 return branch.url
431437
432438
433class BranchView(LaunchpadView, FeedsMixin, BranchMirrorMixin,439class BranchView(InformationTypePortletMixin, FeedsMixin, BranchMirrorMixin,
434 InformationTypePortletMixin):440 LaunchpadView):
435441
436 feed_types = (442 feed_types = (
437 BranchFeedLink,443 BranchFeedLink,
@@ -731,22 +737,21 @@
731 # If we're stacked on a private branch, only show that737 # If we're stacked on a private branch, only show that
732 # information type.738 # information type.
733 if self.context.stacked_on and self.context.stacked_on.private:739 if self.context.stacked_on and self.context.stacked_on.private:
734 shown_types = set([self.context.stacked_on.information_type])740 shown_types = {self.context.stacked_on.information_type}
735 else:741 else:
736 shown_types = (742 shown_types = (
737 InformationType.PUBLIC,743 InformationType.PUBLIC,
744 InformationType.PUBLICSECURITY,
745 InformationType.PRIVATESECURITY,
738 InformationType.USERDATA,746 InformationType.USERDATA,
739 InformationType.PROPRIETARY,747 InformationType.PROPRIETARY,
740 InformationType.EMBARGOED,748 InformationType.EMBARGOED,
741 )749 )
742750
743 # We only show Private Security and Public Security751 # XXX Once Branch Visibility Policies are removed, we only want to
744 # if the branch is linked to a bug with one of those types,752 # show Private (USERDATA) if the branch is linked to such a bug.
745 # as they're confusing and not generally useful otherwise.
746 # Once Proprietary is fully deployed, it should be added here.
747 hidden_types = (753 hidden_types = (
748 InformationType.PUBLICSECURITY,754 # InformationType.USERDATA,
749 InformationType.PRIVATESECURITY,
750 )755 )
751 if set(allowed_types).intersection(hidden_types):756 if set(allowed_types).intersection(hidden_types):
752 params = BugTaskSearchParams(757 params = BugTaskSearchParams(
@@ -800,7 +805,8 @@
800 """See `LaunchpadFormView`"""805 """See `LaunchpadFormView`"""
801 return {self.schema: self.context}806 return {self.schema: self.context}
802807
803 @action('Change Branch', name='change')808 @action('Change Branch', name='change',
809 failure=LaunchpadFormView.ajax_failure_handler)
804 def change_action(self, action, data):810 def change_action(self, action, data):
805 # If the owner or product has changed, add an explicit notification.811 # If the owner or product has changed, add an explicit notification.
806 # We take our own snapshot here to make sure that the snapshot records812 # We take our own snapshot here to make sure that the snapshot records
@@ -849,9 +855,15 @@
849 # was in fact a change.855 # was in fact a change.
850 self.context.date_last_modified = UTC_NOW856 self.context.date_last_modified = UTC_NOW
851857
858 if self.request.is_ajax:
859 return ''
860
852 @property861 @property
853 def next_url(self):862 def next_url(self):
854 return canonical_url(self.context)863 """Return the next URL to call when this call completes."""
864 if not self.request.is_ajax:
865 return canonical_url(self.context)
866 return None
855867
856 cancel_url = next_url868 cancel_url = next_url
857869
@@ -868,6 +880,12 @@
868 field_names = ['lifecycle_status']880 field_names = ['lifecycle_status']
869881
870882
883class BranchEditInformationTypeView(BranchEditFormView):
884 """A view for editing the information type only."""
885
886 field_names = ['information_type']
887
888
871class BranchMirrorStatusView(LaunchpadFormView):889class BranchMirrorStatusView(LaunchpadFormView):
872 """This view displays the mirror status of a branch.890 """This view displays the mirror status of a branch.
873891
@@ -1114,9 +1132,9 @@
1114 def validate(self, data):1132 def validate(self, data):
1115 # Check that we're not moving a team branch to the +junk1133 # Check that we're not moving a team branch to the +junk
1116 # pseudo project.1134 # pseudo project.
1117 owner = data['owner']
1118 if 'name' in data:1135 if 'name' in data:
1119 # Only validate if the name has changed or the owner has changed.1136 # Only validate if the name has changed or the owner has changed.
1137 owner = data['owner']
1120 if ((data['name'] != self.context.name) or1138 if ((data['name'] != self.context.name) or
1121 (owner != self.context.owner)):1139 (owner != self.context.owner)):
1122 # We only allow moving within the same branch target for now.1140 # We only allow moving within the same branch target for now.
11231141
=== modified file 'lib/lp/code/browser/configure.zcml'
--- lib/lp/code/browser/configure.zcml 2012-05-29 06:05:16 +0000
+++ lib/lp/code/browser/configure.zcml 2012-08-29 04:50:28 +0000
@@ -480,6 +480,13 @@
480 permission="launchpad.Edit"480 permission="launchpad.Edit"
481 template="../../app/templates/generic-edit.pt"/>481 template="../../app/templates/generic-edit.pt"/>
482 <browser:page482 <browser:page
483 name="+edit-information-type"
484 for="lp.code.interfaces.branch.IBranch"
485 layer="lp.code.publisher.CodeLayer"
486 class="lp.code.browser.branch.BranchEditInformationTypeView"
487 permission="launchpad.Moderate"
488 template="../../app/templates/generic-edit.pt"/>
489 <browser:page
483 name="+edit"490 name="+edit"
484 for="lp.code.interfaces.branch.IBranch"491 for="lp.code.interfaces.branch.IBranch"
485 layer="lp.code.publisher.CodeLayer"492 layer="lp.code.publisher.CodeLayer"
486493
=== modified file 'lib/lp/code/browser/tests/test_branch.py'
--- lib/lp/code/browser/tests/test_branch.py 2012-08-27 23:58:18 +0000
+++ lib/lp/code/browser/tests/test_branch.py 2012-08-29 04:50:28 +0000
@@ -71,7 +71,10 @@
71 setupBrowser,71 setupBrowser,
72 setupBrowserForUser,72 setupBrowserForUser,
73 )73 )
74from lp.testing.views import create_initialized_view74from lp.testing.views import (
75 create_initialized_view,
76 create_view,
77 )
7578
7679
77class TestBranchMirrorHidden(TestCaseWithFactory):80class TestBranchMirrorHidden(TestCaseWithFactory):
@@ -357,7 +360,8 @@
357 def test_linked_bugs_series_branch_query_scaling(self):360 def test_linked_bugs_series_branch_query_scaling(self):
358 # As we add linked bugs, the query count for a branch index page stays361 # As we add linked bugs, the query count for a branch index page stays
359 # constant.362 # constant.
360 product = self.factory.makeProduct()363 product = self.factory.makeProduct(
364 branch_sharing_policy=BranchSharingPolicy.PUBLIC)
361 branch = self.factory.makeProductBranch(product=product)365 branch = self.factory.makeProductBranch(product=product)
362 browses_under_limit = BrowsesWithQueryLimit(54, branch.owner)366 browses_under_limit = BrowsesWithQueryLimit(54, branch.owner)
363 with person_logged_in(product.owner):367 with person_logged_in(product.owner):
@@ -964,7 +968,7 @@
964 admin = admins.teamowner968 admin = admins.teamowner
965 browser = self.getUserBrowser(969 browser = self.getUserBrowser(
966 canonical_url(branch) + '/+edit', user=admin)970 canonical_url(branch) + '/+edit', user=admin)
967 browser.getControl("Private").click()971 browser.getControl("Private", index=1).click()
968 browser.getControl("Change Branch").click()972 browser.getControl("Change Branch").click()
969 with person_logged_in(person):973 with person_logged_in(person):
970 self.assertEqual(974 self.assertEqual(
@@ -986,6 +990,29 @@
986 self.assertRaises(990 self.assertRaises(
987 LookupError, browser.getControl, "Information Type")991 LookupError, browser.getControl, "Information Type")
988992
993 def test_edit_view_ajax_render(self):
994 # An information type change request is processed as expected when an
995 # XHR request is made to the view.
996 person = self.factory.makePerson()
997 branch = self.factory.makeProductBranch(owner=person)
998
999 extra = {'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'}
1000 request = LaunchpadTestRequest(
1001 method='POST', form={
1002 'field.actions.change': 'Change Branch',
1003 'field.information_type': 'PUBLICSECURITY'},
1004 **extra)
1005 with person_logged_in(person):
1006 view = create_view(
1007 branch, name='+edit-information-type',
1008 request=request, principal=person)
1009 request.traversed_objects = [person, branch.product, branch, view]
1010 view.initialize()
1011 result = view.render()
1012 self.assertEqual('', result)
1013 self.assertEqual(
1014 branch.information_type, InformationType.PUBLICSECURITY)
1015
9891016
990class TestBranchEditViewInformationTypes(TestCaseWithFactory):1017class TestBranchEditViewInformationTypes(TestCaseWithFactory):
991 """Tests for BranchEditView.getInformationTypesToShow."""1018 """Tests for BranchEditView.getInformationTypesToShow."""
@@ -1000,28 +1027,14 @@
1000 self.assertContentEqual(types, view.getInformationTypesToShow())1027 self.assertContentEqual(types, view.getInformationTypesToShow())
10011028
1002 def test_public_branch(self):1029 def test_public_branch(self):
1003 # A normal public branch on a public project can only be public.1030 # A normal public branch on a public project can only be a public
1004 # We don't show information types like Public Security1031 # information type.
1005 # unless there's a linked branch of that type, as they're not
1006 # useful or unconfusing otherwise.
1007 # The model doesn't enforce this, so it's just a UI thing.1032 # The model doesn't enforce this, so it's just a UI thing.
1008 branch = self.factory.makeBranch(1033 branch = self.factory.makeBranch(
1009 information_type=InformationType.PUBLIC)1034 information_type=InformationType.PUBLIC)
1010 self.assertShownTypes([InformationType.PUBLIC], branch)
1011
1012 def test_public_branch_with_security_bug(self):
1013 # A public branch can be set to Public Security if it has a
1014 # linked Public Security bug. The project policy doesn't
1015 # allow private branches, so Private Security and Private
1016 # are unavailable.
1017 branch = self.factory.makeBranch(
1018 information_type=InformationType.PUBLIC)
1019 bug = self.factory.makeBug(
1020 information_type=InformationType.PUBLICSECURITY)
1021 with admin_logged_in():
1022 branch.linkBug(bug, branch.owner)
1023 self.assertShownTypes(1035 self.assertShownTypes(
1024 [InformationType.PUBLIC, InformationType.PUBLICSECURITY],1036 [InformationType.PUBLIC,
1037 InformationType.PUBLICSECURITY],
1025 branch)1038 branch)
10261039
1027 def test_branch_with_disallowed_type(self):1040 def test_branch_with_disallowed_type(self):
@@ -1033,7 +1046,10 @@
1033 branch = self.factory.makeBranch(1046 branch = self.factory.makeBranch(
1034 product=product, information_type=InformationType.PROPRIETARY)1047 product=product, information_type=InformationType.PROPRIETARY)
1035 self.assertShownTypes(1048 self.assertShownTypes(
1036 [InformationType.PUBLIC, InformationType.PROPRIETARY], branch)1049 [InformationType.PUBLIC,
1050 InformationType.PUBLICSECURITY,
1051 InformationType.PROPRIETARY],
1052 branch)
10371053
1038 def test_stacked_on_private(self):1054 def test_stacked_on_private(self):
1039 # A branch stacked on a private branch has its choices limited1055 # A branch stacked on a private branch has its choices limited
@@ -1060,24 +1076,10 @@
1060 branch.product.setBranchVisibilityTeamPolicy(1076 branch.product.setBranchVisibilityTeamPolicy(
1061 branch.owner, BranchVisibilityRule.PRIVATE)1077 branch.owner, BranchVisibilityRule.PRIVATE)
1062 self.assertShownTypes(1078 self.assertShownTypes(
1063 [InformationType.PUBLIC, InformationType.USERDATA], branch)1079 [InformationType.PUBLIC,
10641080 InformationType.PUBLICSECURITY,
1065 def test_private_branch_with_security_bug(self):1081 InformationType.PRIVATESECURITY,
1066 # Branches on projects that allow private branches can use the1082 InformationType.USERDATA],
1067 # Private Security information type if they have a security
1068 # bug linked.
1069 branch = self.factory.makeBranch(
1070 information_type=InformationType.PUBLIC)
1071 with admin_logged_in():
1072 branch.product.setBranchVisibilityTeamPolicy(
1073 branch.owner, BranchVisibilityRule.PRIVATE)
1074 bug = self.factory.makeBug(
1075 information_type=InformationType.PUBLICSECURITY)
1076 with admin_logged_in():
1077 branch.linkBug(bug, branch.owner)
1078 self.assertShownTypes(
1079 [InformationType.PUBLIC, InformationType.PUBLICSECURITY,
1080 InformationType.PRIVATESECURITY, InformationType.USERDATA],
1081 branch)1083 branch)
10821084
1083 def test_branch_for_project_with_embargoed_and_proprietary(self):1085 def test_branch_for_project_with_embargoed_and_proprietary(self):
@@ -1140,6 +1142,7 @@
1140 owner=owner, information_type=InformationType.USERDATA)1142 owner=owner, information_type=InformationType.USERDATA)
1141 with person_logged_in(owner):1143 with person_logged_in(owner):
1142 view = create_initialized_view(branch, '+portlet-privacy')1144 view = create_initialized_view(branch, '+portlet-privacy')
1145 edit_url = '/' + branch.unique_name + '/+edit-information-type'
1143 soup = BeautifulSoup(view.render())1146 soup = BeautifulSoup(view.render())
1144 information_type = soup.find('strong')1147 information_type = soup.find('strong')
1145 description = soup.find('div', id='information-type-description')1148 description = soup.find('div', id='information-type-description')
@@ -1147,3 +1150,5 @@
1147 InformationType.USERDATA.title, information_type.renderContents())1150 InformationType.USERDATA.title, information_type.renderContents())
1148 self.assertTextMatchesExpressionIgnoreWhitespace(1151 self.assertTextMatchesExpressionIgnoreWhitespace(
1149 InformationType.USERDATA.description, description.renderContents())1152 InformationType.USERDATA.description, description.renderContents())
1153 self.assertIsNotNone(
1154 soup.find('a', id='privacy-link', attrs={'href': edit_url}))
11501155
=== modified file 'lib/lp/code/stories/branches/xx-branch-edit-privacy.txt'
--- lib/lp/code/stories/branches/xx-branch-edit-privacy.txt 2012-07-17 14:10:53 +0000
+++ lib/lp/code/stories/branches/xx-branch-edit-privacy.txt 2012-08-29 04:50:28 +0000
@@ -39,7 +39,7 @@
3939
40 >>> admin_browser.open('http://code.launchpad.dev/~eric/fooix/trunk')40 >>> admin_browser.open('http://code.launchpad.dev/~eric/fooix/trunk')
41 >>> admin_browser.getLink('Change branch details').click()41 >>> admin_browser.getLink('Change branch details').click()
42 >>> admin_browser.getControl('Private').click()42 >>> admin_browser.getControl('Private', index=1).click()
43 >>> admin_browser.getControl('Change Branch').click()43 >>> admin_browser.getControl('Change Branch').click()
4444
45The branch is now private, so it assumes the standard private presentation.45The branch is now private, so it assumes the standard private presentation.
@@ -68,7 +68,7 @@
68 >>> browser = setupBrowser(auth='Basic test@canonical.com:test')68 >>> browser = setupBrowser(auth='Basic test@canonical.com:test')
69 >>> browser.open('http://code.launchpad.dev/~name12/firefox/main')69 >>> browser.open('http://code.launchpad.dev/~name12/firefox/main')
70 >>> browser.getLink('Change branch details').click()70 >>> browser.getLink('Change branch details').click()
71 >>> browser.getControl('Private').selected = True71 >>> browser.getControl('Private', index=1).selected = True
72 >>> browser.getControl('Change Branch').click()72 >>> browser.getControl('Change Branch').click()
7373
74Since the branch is now private, it will have the standard Launchpad74Since the branch is now private, it will have the standard Launchpad
@@ -81,7 +81,7 @@
81reverts to its normal presentation.81reverts to its normal presentation.
8282
83 >>> browser.getLink('Change branch details').click()83 >>> browser.getLink('Change branch details').click()
84 >>> browser.getControl('Public').selected = True84 >>> browser.getControl('Public', index=0).selected = True
85 >>> browser.getControl('Change Branch').click()85 >>> browser.getControl('Change Branch').click()
86 >>> print extract_text(find_tag_by_id(browser.contents, 'privacy'))86 >>> print extract_text(find_tag_by_id(browser.contents, 'privacy'))
87 This branch contains Public information...87 This branch contains Public information...
@@ -103,7 +103,7 @@
103Mark the public branch private.103Mark the public branch private.
104104
105 >>> browser.getLink('Change branch details').click()105 >>> browser.getLink('Change branch details').click()
106 >>> browser.getControl('Private').selected = True106 >>> browser.getControl('Private', index=1).selected = True
107 >>> browser.getControl('Change Branch').click()107 >>> browser.getControl('Change Branch').click()
108108
109The branch is now private, so it assumes the standard private presentation.109The branch is now private, so it assumes the standard private presentation.
110110
=== modified file 'lib/lp/code/templates/branch-portlet-privacy.pt'
--- lib/lp/code/templates/branch-portlet-privacy.pt 2012-06-20 05:25:44 +0000
+++ lib/lp/code/templates/branch-portlet-privacy.pt 2012-08-29 04:50:28 +0000
@@ -6,9 +6,15 @@
6 tal:attributes="6 tal:attributes="
7 class python: path('context/private') and 'portlet private' or 'portlet public'7 class python: path('context/private') and 'portlet private' or 'portlet public'
8 "8 "
9 tal:define="link context/menu:context/visibility"
9>10>
10 <span id="information-type-summary"11 <span id="information-type-summary"
11 tal:attributes="class view/information_type_css;">This branch12 tal:attributes="class view/information_type_css;">This branch
12 contains <strong id="information-type" tal:content="view/information_type"></strong> information</span>13 contains
13 <div id="information-type-description" style="padding-top: 5px" tal:content="view/information_type_description"></div>14 <strong id="information-type" tal:content="view/information_type"></strong>
15 information</span>&nbsp;<a class="sprite edit action-icon" id="privacy-link"
16 tal:attributes="href link/path" tal:condition="link/enabled"
17 >Edit</a>
18 <div id="information-type-description" style="padding-top: 5px"
19 tal:content="view/information_type_description"></div>
14</div>20</div>