Merge lp:~cody-somerville/launchpad/bug-444266 into lp:launchpad

Proposed by Cody A.W. Somerville on 2010-04-27
Status: Merged
Approved by: Gavin Panella on 2010-04-28
Approved revision: no longer in the source branch.
Merged at revision: 10835
Proposed branch: lp:~cody-somerville/launchpad/bug-444266
Merge into: lp:launchpad
Diff against target: 313 lines (+141/-19)
8 files modified
lib/canonical/launchpad/interfaces/launchpad.py (+3/-2)
lib/lp/bugs/browser/bugsupervisor.py (+20/-3)
lib/lp/bugs/interfaces/bugsupervisor.py (+20/-9)
lib/lp/bugs/stories/webservice/xx-bug-target.txt (+83/-0)
lib/lp/registry/interfaces/distribution.py (+4/-2)
lib/lp/registry/interfaces/product.py (+5/-3)
lib/lp/registry/stories/webservice/xx-distribution.txt (+2/-0)
lib/lp/registry/stories/webservice/xx-project-registry.txt (+4/-0)
To merge this branch: bzr merge lp:~cody-somerville/launchpad/bug-444266
Reviewer Review Type Date Requested Status
Gavin Panella (community) 2010-04-27 Approve on 2010-04-28
Review via email: mp+24273@code.launchpad.net

Commit Message

Expose bug supervisor and security contact via webservices API for projects and distributions.

Description of the Change

Export bug_supervisor and security_contact to implement LP #444266

To post a comment you must log in.
Gavin Panella (allenap) wrote :
Download full text (11.0 KiB)

Hi Cody,

Thanks for doing this! This is a nice branch. I have a few small
comments, that's all. Approved, but please address those issues before
landing it.

Gavin.

> === modified file 'lib/canonical/launchpad/interfaces/launchpad.py'
> --- lib/canonical/launchpad/interfaces/launchpad.py 2010-04-19 14:47:49 +0000
> +++ lib/canonical/launchpad/interfaces/launchpad.py 2010-04-28 16:42:08 +0000
> @@ -14,6 +14,7 @@
> from zope.schema import Bool, Choice, Int, TextLine
> from persistent import IPersistent
>
> +from lazr.restful.declarations import exported
> from lazr.restful.interfaces import IServiceRootResource
> from canonical.launchpad import _
> from canonical.launchpad.fields import PublicPersonChoice
> @@ -439,11 +440,11 @@
> class IHasSecurityContact(Interface):
> """An object that has a security contact."""
>
> - security_contact = PublicPersonChoice(
> + security_contact = exported(PublicPersonChoice(
> title=_("Security Contact"),
> description=_(
> "The person or team who handles security-related bug reports"),
> - required=False, vocabulary='ValidPersonOrTeam')
> + required=False, vocabulary='ValidPersonOrTeam'))
>
>
> class IHasIcon(Interface):
>
> === modified file 'lib/lp/bugs/browser/bugsupervisor.py'
> --- lib/lp/bugs/browser/bugsupervisor.py 2009-11-12 15:33:27 +0000
> +++ lib/lp/bugs/browser/bugsupervisor.py 2010-04-28 16:42:08 +0000
> @@ -7,17 +7,30 @@
>
> __all__ = ['BugSupervisorEditView']
>
> -
> -from lp.bugs.interfaces.bugsupervisor import IHasBugSupervisor
> from canonical.launchpad.webapp import (
> action, canonical_url, LaunchpadEditFormView)
> from canonical.launchpad.webapp.menu import structured
> -
> +from lazr.restful.interface import copy_field, use_template
> +from zope.interface import Interface
> +
> +from lp.bugs.interfaces.bugsupervisor import IHasBugSupervisor
> +

2 blank lines between classes.

> +class BugSupervisorEditSchema(Interface):
> + """Defines the fields for the edit form.
> +
> + This is necessary to make an editable field for bug supervisor as it is
> + defined as read-only in the interface to prevent setting it directly.
> + """
> + use_template(IHasBugSupervisor, include=[
> + 'bug_supervisor',
> + ])

I don't think you need this use_template() call; its effects are
immediately overwritten below.

> + bug_supervisor = copy_field(
> + IHasBugSupervisor['bug_supervisor'], readonly=False)
>

Again, 2 blank lines.

> class BugSupervisorEditView(LaunchpadEditFormView):
> """Browser view class for editing the bug supervisor."""
>
> - schema = IHasBugSupervisor
> + schema = BugSupervisorEditSchema
> field_names = ['bug_supervisor']
>
> @property
> @@ -30,6 +43,11 @@
> """The page title."""
> return self.label
>
> + @property
> + def adapters(self):
> + """See `LaunchpadFormView`"""
> + return {BugSupervisorEditSchema: self.context}

Interesting, I've never seen this before.

> +
> @action('Change', name='change')
> def change_action(self, action, data):
> """Redirect to the target page with a success message...

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/canonical/launchpad/interfaces/launchpad.py'
2--- lib/canonical/launchpad/interfaces/launchpad.py 2010-04-19 14:47:49 +0000
3+++ lib/canonical/launchpad/interfaces/launchpad.py 2010-04-30 17:49:34 +0000
4@@ -14,6 +14,7 @@
5 from zope.schema import Bool, Choice, Int, TextLine
6 from persistent import IPersistent
7
8+from lazr.restful.declarations import exported
9 from lazr.restful.interfaces import IServiceRootResource
10 from canonical.launchpad import _
11 from canonical.launchpad.fields import PublicPersonChoice
12@@ -439,11 +440,11 @@
13 class IHasSecurityContact(Interface):
14 """An object that has a security contact."""
15
16- security_contact = PublicPersonChoice(
17+ security_contact = exported(PublicPersonChoice(
18 title=_("Security Contact"),
19 description=_(
20 "The person or team who handles security-related bug reports"),
21- required=False, vocabulary='ValidPersonOrTeam')
22+ required=False, vocabulary='ValidPersonOrTeam'))
23
24
25 class IHasIcon(Interface):
26
27=== modified file 'lib/lp/bugs/browser/bugsupervisor.py'
28--- lib/lp/bugs/browser/bugsupervisor.py 2009-11-12 15:33:27 +0000
29+++ lib/lp/bugs/browser/bugsupervisor.py 2010-04-30 17:49:34 +0000
30@@ -7,17 +7,29 @@
31
32 __all__ = ['BugSupervisorEditView']
33
34-
35-from lp.bugs.interfaces.bugsupervisor import IHasBugSupervisor
36 from canonical.launchpad.webapp import (
37 action, canonical_url, LaunchpadEditFormView)
38 from canonical.launchpad.webapp.menu import structured
39+from lazr.restful.interface import copy_field, use_template
40+from zope.interface import Interface
41+
42+from lp.bugs.interfaces.bugsupervisor import IHasBugSupervisor
43+
44+
45+class BugSupervisorEditSchema(Interface):
46+ """Defines the fields for the edit form.
47+
48+ This is necessary to make an editable field for bug supervisor as it is
49+ defined as read-only in the interface to prevent setting it directly.
50+ """
51+ bug_supervisor = copy_field(
52+ IHasBugSupervisor['bug_supervisor'], readonly=False)
53
54
55 class BugSupervisorEditView(LaunchpadEditFormView):
56 """Browser view class for editing the bug supervisor."""
57
58- schema = IHasBugSupervisor
59+ schema = BugSupervisorEditSchema
60 field_names = ['bug_supervisor']
61
62 @property
63@@ -30,6 +42,11 @@
64 """The page title."""
65 return self.label
66
67+ @property
68+ def adapters(self):
69+ """See `LaunchpadFormView`"""
70+ return {BugSupervisorEditSchema: self.context}
71+
72 @action('Change', name='change')
73 def change_action(self, action, data):
74 """Redirect to the target page with a success message."""
75
76=== modified file 'lib/lp/bugs/interfaces/bugsupervisor.py'
77--- lib/lp/bugs/interfaces/bugsupervisor.py 2009-12-05 18:50:48 +0000
78+++ lib/lp/bugs/interfaces/bugsupervisor.py 2010-04-30 17:49:34 +0000
79@@ -14,17 +14,28 @@
80 from canonical.launchpad import _
81 from canonical.launchpad.fields import PublicPersonChoice
82
83-from lp.registry.interfaces.structuralsubscription import (
84- IStructuralSubscriptionTarget)
85-
86-
87-class IHasBugSupervisor(IStructuralSubscriptionTarget):
88-
89- bug_supervisor = PublicPersonChoice(
90+from zope.interface import Interface
91+
92+from lazr.restful.declarations import (
93+ REQUEST_USER, call_with, exported, export_write_operation,
94+ mutator_for, operation_parameters)
95+from lazr.restful.interface import copy_field
96+
97+
98+class IHasBugSupervisor(Interface):
99+
100+ bug_supervisor = exported(PublicPersonChoice(
101 title=_("Bug Supervisor"),
102 description=_(
103 "The person or team responsible for bug management."),
104- required=False, vocabulary='ValidPersonOrTeam')
105+ required=False, vocabulary='ValidPersonOrTeam', readonly=True))
106
107- def setBugSupervisor(self, bug_supervisor, user):
108+ @mutator_for(bug_supervisor)
109+ @call_with(user=REQUEST_USER)
110+ @operation_parameters(
111+ bug_supervisor=copy_field(bug_supervisor))
112+ @export_write_operation()
113+ def setBugSupervisor(bug_supervisor, user):
114 """Set the bug contact and create a bug subscription."""
115+
116+
117
118=== modified file 'lib/lp/bugs/stories/webservice/xx-bug-target.txt'
119--- lib/lp/bugs/stories/webservice/xx-bug-target.txt 2009-08-20 04:46:48 +0000
120+++ lib/lp/bugs/stories/webservice/xx-bug-target.txt 2010-04-30 17:49:34 +0000
121@@ -117,3 +117,86 @@
122 ... '/testix', 'application/json',
123 ... dumps({'official_bug_tags': [u'foo', u'bar']}))
124 HTTP/1.1 209 Content Returned...
125+
126+== bug_supervisor ==
127+
128+We can retrieve or set a person or team as the bug supervisor for projects.
129+
130+ >>> firefox_project = webservice.get('/firefox').jsonBody()
131+ >>> print firefox_project['bug_supervisor_link']
132+ None
133+
134+ >>> print webservice.patch(
135+ ... '/firefox', 'application/json',
136+ ... dumps({'bug_supervisor_link': firefox_project['owner_link']}))
137+ HTTP/1.1 209 Content Returned...
138+
139+ >>> firefox_project = webservice.get('/firefox').jsonBody()
140+ >>> print firefox_project['bug_supervisor_link']
141+ http://api.launchpad.dev/beta/~name12
142+
143+We can also do this for distributions.
144+
145+ >>> ubuntutest_dist = webservice.get('/ubuntutest').jsonBody()
146+ >>> print ubuntutest_dist['bug_supervisor_link']
147+ None
148+
149+ >>> print webservice.patch(
150+ ... '/ubuntutest', 'application/json',
151+ ... dumps({'bug_supervisor_link': ubuntutest_dist['owner_link']}))
152+ HTTP/1.1 209 Content Returned...
153+
154+ >>> ubuntutest_dist = webservice.get('/ubuntutest').jsonBody()
155+ >>> print ubuntutest_dist['bug_supervisor_link']
156+ http://api.launchpad.dev/beta/~ubuntu-team
157+
158+Setting the bug supervisor is restricted to owners and launchpad admins.
159+
160+ >>> print user_webservice.patch(
161+ ... '/ubuntutest', 'application/json',
162+ ... dumps({'bug_supervisor_link': None}))
163+ HTTP/1.1 401 Unauthorized
164+ ...
165+ <BLANKLINE>
166+
167+== security_contact ==
168+
169+We can retrieve or set a person or team as the security contact for projects.
170+
171+ >>> firefox_project = webservice.get('/firefox').jsonBody()
172+ >>> print firefox_project['security_contact_link']
173+ None
174+
175+ >>> print webservice.patch(
176+ ... '/firefox', 'application/json',
177+ ... dumps({'security_contact_link': firefox_project['owner_link']}))
178+ HTTP/1.1 209 Content Returned...
179+
180+ >>> firefox_project = webservice.get('/firefox').jsonBody()
181+ >>> print firefox_project['security_contact_link']
182+ http://api.launchpad.dev/beta/~name12
183+
184+We can also do this for distributions.
185+
186+ >>> ubuntutest_dist = webservice.get('/ubuntutest').jsonBody()
187+ >>> print ubuntutest_dist['security_contact_link']
188+ None
189+
190+ >>> print webservice.patch(
191+ ... '/ubuntutest', 'application/json',
192+ ... dumps({'security_contact_link': ubuntutest_dist['owner_link']}))
193+ HTTP/1.1 209 Content Returned...
194+
195+ >>> ubuntutest_dist = webservice.get('/ubuntutest').jsonBody()
196+ >>> print ubuntutest_dist['security_contact_link']
197+ http://api.launchpad.dev/beta/~ubuntu-team
198+
199+Setting the security contact is restricted to owners and launchpad admins.
200+
201+ >>> print user_webservice.patch(
202+ ... '/ubuntutest', 'application/json',
203+ ... dumps({'security_contact_link': None}))
204+ HTTP/1.1 401 Unauthorized
205+ ...
206+ <BLANKLINE>
207+
208
209=== modified file 'lib/lp/registry/interfaces/distribution.py'
210--- lib/lp/registry/interfaces/distribution.py 2010-04-09 00:21:24 +0000
211+++ lib/lp/registry/interfaces/distribution.py 2010-04-30 17:49:34 +0000
212@@ -40,6 +40,7 @@
213 from lp.app.interfaces.headings import IRootContext
214 from lp.registry.interfaces.announcement import IMakesAnnouncements
215 from lp.registry.interfaces.distributionmirror import IDistributionMirror
216+from lp.bugs.interfaces.bugsupervisor import IHasBugSupervisor
217 from lp.bugs.interfaces.bugtarget import (
218 IBugTarget, IOfficialBugTagTargetPublic, IOfficialBugTagTargetRestricted)
219 from lp.soyuz.interfaces.buildrecords import IHasBuildRecords
220@@ -548,8 +549,9 @@
221 """Can the user edit this distribution?"""
222
223
224-class IDistribution(IDistributionEditRestricted, IDistributionPublic,
225- IRootContext, IStructuralSubscriptionTarget):
226+class IDistribution(
227+ IDistributionEditRestricted, IDistributionPublic, IHasBugSupervisor,
228+ IRootContext, IStructuralSubscriptionTarget):
229 """An operating system distribution."""
230 export_as_webservice_entry()
231
232
233=== modified file 'lib/lp/registry/interfaces/product.py'
234--- lib/lp/registry/interfaces/product.py 2010-04-23 02:55:08 +0000
235+++ lib/lp/registry/interfaces/product.py 2010-04-30 17:49:34 +0000
236@@ -38,6 +38,7 @@
237 Description, IconImageUpload, LogoImageUpload, MugshotImageUpload,
238 ParticipatingPersonChoice, ProductBugTracker, ProductNameField,
239 PublicPersonChoice, Summary, Title, URIField)
240+from lp.bugs.interfaces.bugsupervisor import IHasBugSupervisor
241 from lp.registry.interfaces.structuralsubscription import (
242 IStructuralSubscriptionTarget)
243 from lp.app.interfaces.headings import IRootContext
244@@ -715,9 +716,10 @@
245 """Return basic timeline data useful for creating a diagram."""
246
247
248-class IProduct(IProductEditRestricted, IProductProjectReviewRestricted,
249- IProductDriverRestricted, IProductPublic, IRootContext,
250- IStructuralSubscriptionTarget):
251+class IProduct(
252+ IHasBugSupervisor, IProductEditRestricted,
253+ IProductProjectReviewRestricted, IProductDriverRestricted,
254+ IProductPublic, IRootContext, IStructuralSubscriptionTarget):
255 """A Product.
256
257 The Launchpad Registry describes the open source world as ProjectGroups
258
259=== modified file 'lib/lp/registry/stories/webservice/xx-distribution.txt'
260--- lib/lp/registry/stories/webservice/xx-distribution.txt 2010-03-29 16:44:37 +0000
261+++ lib/lp/registry/stories/webservice/xx-distribution.txt 2010-04-30 17:49:34 +0000
262@@ -24,6 +24,7 @@
263 archive_mirrors_collection_link: u'http://.../ubuntu/archive_mirrors'
264 archives_collection_link: u'http://.../ubuntu/archives'
265 bug_reporting_guidelines: None
266+ bug_supervisor_link: None
267 cdimage_mirrors_collection_link: u'http://.../ubuntu/cdimage_mirrors'
268 current_series_link: u'http://.../ubuntu/hoary'
269 date_created: u'2006-10-16T18:31:43.415195+00:00'
270@@ -41,6 +42,7 @@
271 official_bug_tags: []
272 owner_link: u'http://.../~ubuntu-team'
273 resource_type_link: u'http://.../#distribution'
274+ security_contact_link: None
275 self_link: u'http://.../ubuntu'
276 series_collection_link: u'http://.../ubuntu/series'
277 summary: u'Ubuntu is a new approach to Linux Distribution...'
278
279=== modified file 'lib/lp/registry/stories/webservice/xx-project-registry.txt'
280--- lib/lp/registry/stories/webservice/xx-project-registry.txt 2010-04-23 20:41:06 +0000
281+++ lib/lp/registry/stories/webservice/xx-project-registry.txt 2010-04-30 17:49:34 +0000
282@@ -151,6 +151,7 @@
283 all_milestones_collection_link: u'http://.../firefox/all_milestones'
284 brand_link: u'http://.../firefox/brand'
285 bug_reporting_guidelines: None
286+ bug_supervisor_link: None
287 bug_tracker_link: None
288 commercial_subscription_is_due: True
289 commercial_subscription_link: None
290@@ -181,6 +182,7 @@
291 resource_type_link: u'http://.../#project'
292 reviewer_whiteboard: None
293 screenshots_url: None
294+ security_contact_link: None
295 self_link: u'http://.../firefox'
296 series_collection_link: u'http://.../firefox/series'
297 sourceforge_project: None
298@@ -210,6 +212,7 @@
299 all_milestones_collection_link: u'http://.../firefox/all_milestones'
300 brand_link: u'http://.../firefox/brand'
301 bug_reporting_guidelines: None
302+ bug_supervisor_link: None
303 bug_tracker_link: None
304 commercial_subscription_is_due: True
305 commercial_subscription_link: None
306@@ -240,6 +243,7 @@
307 resource_type_link: u'http://.../#project'
308 reviewer_whiteboard:...redacted...
309 screenshots_url: None
310+ security_contact_link: None
311 self_link: u'http://.../firefox'
312 series_collection_link: u'http://.../firefox/series'
313 sourceforge_project: None