Merge lp:~sinzui/launchpad/distro-driver into lp:launchpad
- distro-driver
- Merge into devel
Proposed by
Curtis Hovey
Status: | Merged |
---|---|
Merged at revision: | not available |
Proposed branch: | lp:~sinzui/launchpad/distro-driver |
Merge into: | lp:launchpad |
Diff against target: | None lines |
To merge this branch: | bzr merge lp:~sinzui/launchpad/distro-driver |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Deryck Hodge (community) | Approve | ||
Review via email: mp+9909@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Revision history for this message
Curtis Hovey (sinzui) wrote : | # |
Revision history for this message
Deryck Hodge (deryck) wrote : | # |
Hi, Curtis.
As I mentioned on IRC, because of https:/
and the creation of DriveDistributi
I did wonder if that should be moved to an app-specific security.py file.
As you noted, "Not until there a real plan to do this. This is foundations task."
I only mention it here for the sake of completeness.
Otherwise, this looks very good.
Cheers,
deryck
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/security.py' |
2 | --- lib/canonical/launchpad/security.py 2009-08-05 14:52:50 +0000 |
3 | +++ lib/canonical/launchpad/security.py 2009-08-10 00:54:34 +0000 |
4 | @@ -765,6 +765,25 @@ |
5 | user.inTeam(admins)) |
6 | |
7 | |
8 | +class DriveDistributionByDriversOrOwnersOrAdmins(AuthorizationBase): |
9 | + """Distribution drivers, owners, and admins may plan releases. |
10 | + |
11 | + Drivers of `IDerivativeDistribution`s can create series. Owners and |
12 | + admins can create series for all `IDistribution`s. |
13 | + """ |
14 | + permission = 'launchpad.Driver' |
15 | + usedfor = IDistribution |
16 | + |
17 | + def checkAuthenticated(self, user): |
18 | + if user.inTeam(self.obj.driver) and not self.obj.full_functionality: |
19 | + # Drivers of derivative distributions can create a series that |
20 | + # they will be the release manager for. |
21 | + return True |
22 | + admins = getUtility(ILaunchpadCelebrities).admin |
23 | + return (user.inTeam(self.obj.owner) or |
24 | + user.inTeam(admins)) |
25 | + |
26 | + |
27 | class EditDistributionSourcePackageByDistroOwnersOrAdmins(AuthorizationBase): |
28 | """The owner of a distribution should be able to edit its source |
29 | package information""" |
30 | @@ -803,6 +822,11 @@ |
31 | usedfor = IDistroSeries |
32 | |
33 | def checkAuthenticated(self, user): |
34 | + if (user.inTeam(self.obj.driver) |
35 | + and not self.obj.distribution.full_functionality): |
36 | + # The series driver (release manager) may edit a series if the |
37 | + # distribution is an `IDerivativeDistribution` |
38 | + return True |
39 | admins = getUtility(ILaunchpadCelebrities).admin |
40 | return (user.inTeam(self.obj.owner) or |
41 | user.inTeam(self.obj.distribution.owner) or |
42 | |
43 | === modified file 'lib/lp/registry/browser/configure.zcml' |
44 | --- lib/lp/registry/browser/configure.zcml 2009-08-06 18:17:49 +0000 |
45 | +++ lib/lp/registry/browser/configure.zcml 2009-08-09 18:01:59 +0000 |
46 | @@ -1948,6 +1948,16 @@ |
47 | permission="launchpad.Admin" |
48 | template="../templates/distroseries-add.pt"> |
49 | </browser:page> |
50 | + |
51 | + <browser:page |
52 | + name="+addseries" |
53 | + for="lp.registry.interfaces.distribution.IDerivativeDistribution" |
54 | + class="lp.registry.browser.distroseries.DistroSeriesAddView" |
55 | + facet="overview" |
56 | + permission="launchpad.Driver" |
57 | + template="../templates/distroseries-add.pt"> |
58 | + </browser:page> |
59 | + |
60 | <browser:page |
61 | for="lp.registry.interfaces.distribution.IDistribution" |
62 | permission="launchpad.Edit" |
63 | |
64 | === modified file 'lib/lp/registry/browser/distroseries.py' |
65 | --- lib/lp/registry/browser/distroseries.py 2009-07-17 18:46:25 +0000 |
66 | +++ lib/lp/registry/browser/distroseries.py 2009-08-10 05:33:31 +0000 |
67 | @@ -267,6 +267,47 @@ |
68 | return self.context.searchPackages(self.text) |
69 | |
70 | |
71 | +class DistroSeriesStatusMixin: |
72 | + """A mixin that provides status field support.""" |
73 | + |
74 | + def createStatusField(self): |
75 | + """Create the 'status' field. |
76 | + |
77 | + Create the status vocabulary according the current distroseries |
78 | + status: |
79 | + * stable -> CURRENT, SUPPORTED, OBSOLETE |
80 | + * unstable -> EXPERIMENTAL, DEVELOPMENT, FROZEN, FUTURE, CURRENT |
81 | + """ |
82 | + stable_status = ( |
83 | + DistroSeriesStatus.CURRENT, |
84 | + DistroSeriesStatus.SUPPORTED, |
85 | + DistroSeriesStatus.OBSOLETE, |
86 | + ) |
87 | + |
88 | + if self.context.status not in stable_status: |
89 | + terms = [status for status in DistroSeriesStatus.items |
90 | + if status not in stable_status] |
91 | + terms.append(DistroSeriesStatus.CURRENT) |
92 | + else: |
93 | + terms = stable_status |
94 | + |
95 | + status_vocabulary = SimpleVocabulary( |
96 | + [SimpleTerm(item, item.name, item.title) for item in terms]) |
97 | + |
98 | + return form.Fields( |
99 | + Choice(__name__='status', |
100 | + title=_('Status'), |
101 | + vocabulary=status_vocabulary, |
102 | + description=_("Select the distroseries status."), |
103 | + required=True)) |
104 | + |
105 | + def updateDateReleased(self, status): |
106 | + """Update the datereleased field if the status is set to CURRENT.""" |
107 | + if (self.context.datereleased is None and |
108 | + status == DistroSeriesStatus.CURRENT): |
109 | + self.context.datereleased = UTC_NOW |
110 | + |
111 | + |
112 | class DistroSeriesView(BuildRecordsView, QueueItemsView): |
113 | |
114 | def initialize(self): |
115 | @@ -308,33 +349,44 @@ |
116 | return True |
117 | |
118 | |
119 | -class DistroSeriesEditView(LaunchpadEditFormView): |
120 | +class DistroSeriesEditView(LaunchpadEditFormView, DistroSeriesStatusMixin): |
121 | """View class that lets you edit a DistroSeries object. |
122 | |
123 | It redirects to the main distroseries page after a successful edit. |
124 | """ |
125 | schema = IDistroSeries |
126 | field_names = ['displayname', 'title', 'summary', 'description'] |
127 | - |
128 | - def initialize(self): |
129 | - """See `LaunchpadEditFormView`. |
130 | - |
131 | - Additionally set the 'label' attribute which will be used in the |
132 | - template. |
133 | + custom_widget('status', LaunchpadDropdownWidget) |
134 | + |
135 | + @property |
136 | + def label(self): |
137 | + """See `LaunchpadFormView`.""" |
138 | + return 'Change %s details' % self.context.title |
139 | + |
140 | + def setUpFields(self): |
141 | + """See `LaunchpadFormView`. |
142 | + |
143 | + In addition to setting schema fields, also initialize the |
144 | + 'status' field. See `createStatusField` method. |
145 | """ |
146 | - LaunchpadEditFormView.initialize(self) |
147 | - self.label = 'Change %s details' % self.context.title |
148 | + LaunchpadEditFormView.setUpFields(self) |
149 | + if not self.context.distribution.full_functionality: |
150 | + # This is an IDerivativeDistribution which may set its status. |
151 | + self.form_fields = ( |
152 | + self.form_fields + self.createStatusField()) |
153 | |
154 | @action("Change") |
155 | def change_action(self, action, data): |
156 | """Update the context and redirects to its overviw page.""" |
157 | + if not self.context.distribution.full_functionality: |
158 | + self.updateDateReleased(data.get('status')) |
159 | self.updateContextFromData(data) |
160 | self.request.response.addInfoNotification( |
161 | 'Your changes have been applied.') |
162 | self.next_url = canonical_url(self.context) |
163 | |
164 | |
165 | -class DistroSeriesAdminView(LaunchpadEditFormView): |
166 | +class DistroSeriesAdminView(LaunchpadEditFormView, DistroSeriesStatusMixin): |
167 | """View class for administering a DistroSeries object. |
168 | |
169 | It redirects to the main distroseries page after a successful edit. |
170 | @@ -343,14 +395,10 @@ |
171 | field_names = ['name', 'version', 'changeslist'] |
172 | custom_widget('status', LaunchpadDropdownWidget) |
173 | |
174 | - def initialize(self): |
175 | - """See `LaunchpadEditFormView`. |
176 | - |
177 | - Additionally set the 'label' attribute which will be used in the |
178 | - template. |
179 | - """ |
180 | - LaunchpadEditFormView.initialize(self) |
181 | - self.label = 'Administer %s' % self.context.title |
182 | + @property |
183 | + def label(self): |
184 | + """See `LaunchpadFormView`.""" |
185 | + return 'Administer %s' % self.context.title |
186 | |
187 | def setUpFields(self): |
188 | """Override `LaunchpadFormView`. |
189 | @@ -362,37 +410,6 @@ |
190 | self.form_fields = ( |
191 | self.form_fields + self.createStatusField()) |
192 | |
193 | - def createStatusField(self): |
194 | - """Create the 'status' field. |
195 | - |
196 | - Create the status vocabulary according the current distroseries |
197 | - status: |
198 | - * stable -> CURRENT, SUPPORTED, OBSOLETE |
199 | - * unstable -> EXPERIMENTAL, DEVELOPMENT, FROZEN, FUTURE, CURRENT |
200 | - """ |
201 | - stable_status = ( |
202 | - DistroSeriesStatus.CURRENT, |
203 | - DistroSeriesStatus.SUPPORTED, |
204 | - DistroSeriesStatus.OBSOLETE, |
205 | - ) |
206 | - |
207 | - if self.context.status not in stable_status: |
208 | - terms = [status for status in DistroSeriesStatus.items |
209 | - if status not in stable_status] |
210 | - terms.append(DistroSeriesStatus.CURRENT) |
211 | - else: |
212 | - terms = stable_status |
213 | - |
214 | - status_vocabulary = SimpleVocabulary( |
215 | - [SimpleTerm(item, item.name, item.title) for item in terms]) |
216 | - |
217 | - return form.Fields( |
218 | - Choice(__name__='status', |
219 | - title=_('Status'), |
220 | - vocabulary=status_vocabulary, |
221 | - description=_("Select the distroseries status."), |
222 | - required=True)) |
223 | - |
224 | @action("Change") |
225 | def change_action(self, action, data): |
226 | """Update the context and redirects to its overviw page. |
227 | @@ -400,11 +417,7 @@ |
228 | Also, set 'datereleased' when a unstable distroseries is made |
229 | CURRENT. |
230 | """ |
231 | - status = data.get('status') |
232 | - if (self.context.datereleased is None and |
233 | - status == DistroSeriesStatus.CURRENT): |
234 | - self.context.datereleased = UTC_NOW |
235 | - |
236 | + self.updateDateReleased(data.get('status')) |
237 | self.updateContextFromData(data) |
238 | |
239 | self.request.response.addInfoNotification( |
240 | |
241 | === modified file 'lib/lp/registry/browser/tests/distroseries-views.txt' |
242 | --- lib/lp/registry/browser/tests/distroseries-views.txt 2009-08-01 15:40:52 +0000 |
243 | +++ lib/lp/registry/browser/tests/distroseries-views.txt 2009-08-10 05:33:31 +0000 |
244 | @@ -1,4 +1,5 @@ |
245 | -= DistroSeries view classes = |
246 | +DistroSeries view classes |
247 | +========================= |
248 | |
249 | >>> from zope.component import getMultiAdapter |
250 | >>> from canonical.launchpad.webapp.servers import LaunchpadTestRequest |
251 | @@ -137,11 +138,44 @@ |
252 | True |
253 | |
254 | |
255 | +Editing distroseries |
256 | +-------------------- |
257 | + |
258 | +The distroseries edit view allows the editor to change series. The form |
259 | +uses the displayname, title, and description fields. |
260 | + |
261 | + >>> view = create_initialized_view(hoary, '+edit') |
262 | + >>> print view.label |
263 | + Change The Hoary Hedgehog Release details |
264 | + |
265 | + >>> [field.__name__ for field in view.form_fields] |
266 | + ['displayname', 'title', 'summary', 'description'] |
267 | + |
268 | +Series that belong to derivative distributions also contain the status field. |
269 | + |
270 | + >>> youbuntu = factory.makeDistribution(name='youbuntu') |
271 | + >>> yo_series = factory.makeDistroRelease(name='melon') |
272 | + >>> youbuntu.full_functionality |
273 | + False |
274 | + |
275 | + >>> yo_driver = factory.makePerson(name='yo-driver') |
276 | + >>> youbuntu.driver = yo_driver |
277 | + >>> login_person(yo_driver) |
278 | + >>> view = create_initialized_view(yo_series, '+edit') |
279 | + >>> print view.label |
280 | + Change ... details |
281 | + |
282 | + >>> [field.__name__ for field in view.form_fields] |
283 | + ['displayname', 'title', 'summary', 'description', 'status'] |
284 | + |
285 | + |
286 | Creating distroseries |
287 | --------------------- |
288 | |
289 | A distroseries is created using the distroseries view. |
290 | |
291 | + >>> login('foo.bar@canonical.com') |
292 | + |
293 | >>> view = create_view(ubuntu, '+addseries') |
294 | >>> print view.page_title |
295 | Register a series in Ubuntu |
296 | @@ -179,11 +213,94 @@ |
297 | # Save this series to test name and version constraints. |
298 | >>> transaction.commit() |
299 | |
300 | + |
301 | +Drivers can create distroseries |
302 | +------------------------------- |
303 | + |
304 | +Users who are appointed as drivers of a distribution can create a series. |
305 | +Most distributions are an `IDerivativeDistribution`. |
306 | + |
307 | + >>> from canonical.launchpad.webapp.authorization import check_permission |
308 | + |
309 | + >>> login_person(yo_driver) |
310 | + >>> view = create_view(youbuntu, name='+addseries') |
311 | + >>> check_permission('launchpad.Driver', view) |
312 | + True |
313 | + |
314 | + >>> yo_form = dict(form) |
315 | + >>> yo_form['field.name'] = 'island' |
316 | + >>> yo_form['field.displayname'] = 'Island' |
317 | + >>> yo_form['field.title'] = 'YouBuntu Island' |
318 | + >>> view = create_initialized_view( |
319 | + ... youbuntu, name='+addseries', form=yo_form, principal=yo_driver) |
320 | + >>> view.errors |
321 | + [] |
322 | + |
323 | + >>> yo_series = youbuntu.getSeries('island') |
324 | + >>> print yo_series.displayname |
325 | + Island |
326 | + |
327 | +Ubuntu is an exception. It's drivers cannot create series because Ubuntu |
328 | +is an `IBaseDistribution`. |
329 | + |
330 | + >>> ubuntu.full_functionality |
331 | + True |
332 | + |
333 | + >>> login_person(ubuntu.owner.teamowner) |
334 | + >>> ubuntu.driver = yo_driver |
335 | + >>> login_person(yo_driver) |
336 | + >>> view = create_view(youbuntu, name='+addseries') |
337 | + >>> check_permission('launchpad.Edit', view) |
338 | + False |
339 | + |
340 | + |
341 | +Editing Distroseries |
342 | +-------------------- |
343 | + |
344 | +The series driver (release manager) can edit a series if the series belongs |
345 | +to a `IDerivativeDistribution`. |
346 | + |
347 | + >>> print yo_series.driver.name |
348 | + yo-driver |
349 | + |
350 | + >>> login_person(yo_driver) |
351 | + >>> view = create_view(yo_series, name='+edit') |
352 | + >>> check_permission('launchpad.Edit', view) |
353 | + True |
354 | + |
355 | + >>> yo_form = dict(form) |
356 | + >>> del yo_form['field.actions.create'] |
357 | + >>> yo_form['field.displayname'] = 'Mountain' |
358 | + >>> yo_form['field.title'] = 'YouBuntu Mountain' |
359 | + >>> yo_form['field.summary'] = 'Mountain summary' |
360 | + >>> yo_form['field.description'] = 'Mountain description' |
361 | + >>> yo_form['field.actions.change'] = 'Change' |
362 | + >>> view = create_initialized_view( |
363 | + ... yo_series, name='+edit', form=yo_form, principal=yo_driver) |
364 | + >>> view.errors |
365 | + [] |
366 | + |
367 | + >>> print yo_series.displayname |
368 | + Mountain |
369 | + |
370 | +Drivers of an `IBaseDistribution` such as Ubuntu cannot edit a series. |
371 | + |
372 | + >>> login_person(ubuntu.owner.teamowner) |
373 | + >>> hoary.driver = yo_driver |
374 | + >>> login_person(yo_driver) |
375 | + |
376 | + >>> view = create_view(hoary, name='+edit') |
377 | + >>> check_permission('launchpad.Edit', view) |
378 | + False |
379 | + |
380 | + |
381 | Distroseries name |
382 | ----------------- |
383 | |
384 | The distroseries name is unique. |
385 | |
386 | + >>> login('foo.bar@canonical.com') |
387 | + |
388 | >>> form['field.name'] = 'sane' |
389 | >>> form['field.version'] = '2009.07' |
390 | >>> view = create_initialized_view(ubuntu, '+addseries', form=form) |
391 | |
392 | === modified file 'lib/lp/registry/browser/tests/milestone-views.txt' |
393 | --- lib/lp/registry/browser/tests/milestone-views.txt 2009-06-13 15:23:18 +0000 |
394 | +++ lib/lp/registry/browser/tests/milestone-views.txt 2009-08-10 02:29:03 +0000 |
395 | @@ -361,11 +361,58 @@ |
396 | 1.1 None None |
397 | |
398 | |
399 | -== Deleting milestones == |
400 | +Distroseries driver and milestones |
401 | +---------------------------------- |
402 | + |
403 | +The driver of a series that belongs to an `IDerivativeDistribution` is a |
404 | +release manager and can create milestones. |
405 | + |
406 | + >>> distroseries = factory.makeDistroRelease(name='pumpkin') |
407 | + >>> driver = factory.makePerson(name='a-driver') |
408 | + >>> distroseries.driver = driver |
409 | + >>> login_person(driver) |
410 | + |
411 | + >>> form = { |
412 | + ... 'field.name': 'pie', |
413 | + ... 'field.actions.register': 'Register Milestone', |
414 | + ... } |
415 | + >>> view = create_initialized_view( |
416 | + ... distroseries, '+addmilestone', form=form) |
417 | + >>> milestone = distroseries.milestones[0] |
418 | + >>> print milestone.name |
419 | + pie |
420 | + |
421 | +The driver has access to the milestone. |
422 | + |
423 | + >>> view = create_initialized_view(milestone, '+edit') |
424 | + >>> check_permission('launchpad.Edit', view) |
425 | + True |
426 | + |
427 | +The driver of an `IBaseDistribution` such as Ubuntu cannot create a milestone. |
428 | + |
429 | + >>> login_person(ubuntu_distro.owner.teamowner) |
430 | + >>> hoary_series.driver = driver |
431 | + >>> login_person(driver) |
432 | + |
433 | + >>> view = create_initialized_view(hoary_series, '+addmilestone') |
434 | + >>> check_permission('launchpad.Edit', view) |
435 | + False |
436 | + |
437 | +Nor can the driver edit it. |
438 | + |
439 | + >>> milestone = factory.makeMilestone(distribution=ubuntu_distro) |
440 | + >>> view = create_initialized_view(milestone, '+edit') |
441 | + >>> check_permission('launchpad.Edit', view) |
442 | + False |
443 | + |
444 | + |
445 | +Deleting milestones |
446 | +------------------- |
447 | |
448 | The DeleteMilestoneView allows users to edit permissions to delete Milestones. |
449 | The view is restricted to owners of the project and drivers of the series. |
450 | |
451 | + >>> login_person(owner) |
452 | >>> milestone = firefox_1_0.newMilestone('1.0.10') |
453 | >>> print milestone.name |
454 | 1.0.10 |
455 | |
456 | === modified file 'lib/lp/registry/configure.zcml' |
457 | --- lib/lp/registry/configure.zcml 2009-08-05 01:53:16 +0000 |
458 | +++ lib/lp/registry/configure.zcml 2009-08-09 18:01:59 +0000 |
459 | @@ -1360,6 +1360,9 @@ |
460 | permission="launchpad.Edit" |
461 | interface="lp.registry.interfaces.distribution.IDistributionEditRestricted"/> |
462 | <require |
463 | + permission="launchpad.Driver" |
464 | + interface="lp.registry.interfaces.distribution.IDistributionDriverRestricted"/> |
465 | + <require |
466 | permission="launchpad.Edit" |
467 | set_attributes="displayname title summary description translation_focus translationgroup translationpermission driver members owner security_contact mirror_admin homepage_content icon logo mugshot enable_bug_expiration bug_reporting_guidelines official_blueprints official_malone official_rosetta official_answers official_bug_tags"/> |
468 | <require |
469 | |
470 | === modified file 'lib/lp/registry/doc/distroseries.txt' |
471 | --- lib/lp/registry/doc/distroseries.txt 2009-07-24 12:55:03 +0000 |
472 | +++ lib/lp/registry/doc/distroseries.txt 2009-08-10 03:39:09 +0000 |
473 | @@ -1,4 +1,4 @@ |
474 | -= Distro Releases = |
475 | += DistroSeries = |
476 | |
477 | From the DerivationOverview spec |
478 | <https://launchpad.canonical.com/DerivationOverview>: |
479 | @@ -762,7 +762,75 @@ |
480 | (u'mozilla-firefox-data', u'0.9') |
481 | |
482 | |
483 | -= Specification Listings = |
484 | +Creating DistroSeries |
485 | +--------------------- |
486 | + |
487 | +Users with launchpad.Driver permission may create DistroSeries. In the |
488 | +case of an `IDerivativeDistribution` A user who is a driver can create |
489 | +the series and he is automatically assigned to the series' driver role |
490 | +so that he can edit it. |
491 | + |
492 | + >>> youbuntu = factory.makeDistribution(name='youbuntu') |
493 | + >>> yo_driver = factory.makePerson(name='yo-driver') |
494 | + >>> youbuntu.driver = yo_driver |
495 | + >>> login_person(yo_driver) |
496 | + >>> youbuntu.full_functionality |
497 | + False |
498 | + |
499 | + >>> yo_series = youbuntu.newSeries( |
500 | + ... name='island', displayname='Island', title='YouBuntu Island', |
501 | + ... summary='summary', description='description', version='09.07', |
502 | + ... parent_series=warty, owner=yo_driver) |
503 | + >>> print yo_series.name |
504 | + island |
505 | + >>> print yo_series.driver.name |
506 | + yo-driver |
507 | + |
508 | +Owners of derivative distributions, and admins can create series too, but |
509 | +they are not automatically set as the series driver because they always |
510 | +have permission to edit the series. |
511 | + |
512 | + >>> login_person(youbuntu.owner) |
513 | + >>> yo_series = youbuntu.newSeries( |
514 | + ... name='forest', displayname='Forest', title='YouBuntu Forest', |
515 | + ... summary='summary', description='description', version='09.07', |
516 | + ... parent_series=warty, owner=youbuntu.owner) |
517 | + >>> print yo_series.name |
518 | + forest |
519 | + >>> print yo_series.driver |
520 | + None |
521 | + |
522 | +Ubuntu is an `IBaseDistribution` which requires special preparation for |
523 | +Soyuz and Translations before a series can be created. Ubuntu driver can |
524 | +not create series. |
525 | + |
526 | + >>> login_person(ubuntu.owner.activemembers[0]) |
527 | + >>> ubuntu.driver = yo_driver |
528 | + >>> login_person(yo_driver) |
529 | + >>> ubuntu.newSeries( |
530 | + ... name='finch', displayname='Finch', title='Ubuntu Finch', |
531 | + ... summary='summary', description='description', version='9.06', |
532 | + ... parent_series=warty, owner=ubuntu.driver) |
533 | + Traceback (most recent call last): |
534 | + ... |
535 | + Unauthorized: ... |
536 | + |
537 | +Owners and admins of base distributions are the only users who can create a |
538 | +series. |
539 | + |
540 | + >>> login_person(ubuntu.owner.activemembers[0]) |
541 | + >>> u_series = ubuntu.newSeries( |
542 | + ... name='finch', displayname='Finch', title='Ubuntu Finch', |
543 | + ... summary='summary', description='description', version='9.06', |
544 | + ... parent_series=warty, owner=ubuntu.owner) |
545 | + >>> print u_series.name |
546 | + finch |
547 | + >>> print u_series.driver |
548 | + None |
549 | + |
550 | + |
551 | +Specification Listings |
552 | +---------------------- |
553 | |
554 | We should be able to get lists of specifications in different states |
555 | related to a distroseries. |
556 | |
557 | === modified file 'lib/lp/registry/doc/productseries.txt' |
558 | --- lib/lp/registry/doc/productseries.txt 2009-07-23 13:44:13 +0000 |
559 | +++ lib/lp/registry/doc/productseries.txt 2009-08-10 03:39:09 +0000 |
560 | @@ -75,9 +75,14 @@ |
561 | >>> login_person(firefox.owner) |
562 | >>> emacs_series = firefox.newSeries(firefox.owner , 'emacs', summary) |
563 | |
564 | +When a driver creates a series, he is also the driver of the new series |
565 | +to make him the release manager. |
566 | + |
567 | >>> firefox.driver = series_driver |
568 | >>> login_person(series_driver) |
569 | >>> emacs2 = firefox.newSeries(series_driver , 'emacs2', summary) |
570 | + >>> print emacs2.driver.name |
571 | + driver |
572 | |
573 | A newly created series is assumed to be in the development state. |
574 | |
575 | |
576 | === modified file 'lib/lp/registry/interfaces/distribution.py' |
577 | --- lib/lp/registry/interfaces/distribution.py 2009-08-03 13:00:37 +0000 |
578 | +++ lib/lp/registry/interfaces/distribution.py 2009-08-09 18:01:59 +0000 |
579 | @@ -8,7 +8,10 @@ |
580 | __metaclass__ = type |
581 | |
582 | __all__ = [ |
583 | + 'IBaseDistribution', |
584 | + 'IDerivativeDistribution', |
585 | 'IDistribution', |
586 | + 'IDistributionDriverRestricted', |
587 | 'IDistributionEditRestricted', |
588 | 'IDistributionMirrorMenuMarker', |
589 | 'IDistributionPublic', |
590 | @@ -70,6 +73,10 @@ |
591 | class IDistributionEditRestricted(IOfficialBugTagTargetRestricted): |
592 | """IDistribution properties requiring launchpad.Edit permission.""" |
593 | |
594 | + |
595 | +class IDistributionDriverRestricted(Interface): |
596 | + """IDistribution properties requiring launchpad.Driver permission.""" |
597 | + |
598 | def newSeries(name, displayname, title, summary, description, |
599 | version, parent_series, owner): |
600 | """Creates a new distroseries.""" |
601 | @@ -533,6 +540,14 @@ |
602 | IDistribution._v_attrs['official_bug_tags'] = writable_obt_field |
603 | |
604 | |
605 | +class IBaseDistribution(IDistribution): |
606 | + """A Distribution that is the base for other Distributions.""" |
607 | + |
608 | + |
609 | +class IDerivativeDistribution(IDistribution): |
610 | + """A Distribution that derives from another Distribution.""" |
611 | + |
612 | + |
613 | class IDistributionSet(Interface): |
614 | """Interface for DistrosSet""" |
615 | export_as_webservice_collection(IDistribution) |
616 | |
617 | === modified file 'lib/lp/registry/model/distribution.py' |
618 | --- lib/lp/registry/model/distribution.py 2009-08-03 13:00:37 +0000 |
619 | +++ lib/lp/registry/model/distribution.py 2009-08-09 21:14:27 +0000 |
620 | @@ -7,7 +7,7 @@ |
621 | __metaclass__ = type |
622 | __all__ = ['Distribution', 'DistributionSet'] |
623 | |
624 | -from zope.interface import implements |
625 | +from zope.interface import alsoProvides, implements |
626 | from zope.component import getUtility |
627 | |
628 | from sqlobject import ( |
629 | @@ -82,7 +82,8 @@ |
630 | from lp.soyuz.interfaces.build import IBuildSet |
631 | from lp.soyuz.interfaces.buildrecords import IHasBuildRecords |
632 | from lp.registry.interfaces.distribution import ( |
633 | - IDistribution, IDistributionSet) |
634 | + IBaseDistribution, IDerivativeDistribution, IDistribution, |
635 | + IDistributionSet) |
636 | from lp.registry.interfaces.distributionmirror import ( |
637 | IDistributionMirror, MirrorContent, MirrorStatus) |
638 | from lp.registry.interfaces.distroseries import ( |
639 | @@ -183,6 +184,18 @@ |
640 | default=False) |
641 | active = True # Required by IPillar interface. |
642 | |
643 | + |
644 | + def _init(self, *args, **kw): |
645 | + """Initialize an `IBaseDistribution` or `IDerivativeDistribution`.""" |
646 | + SQLBase._init(self, *args, **kw) |
647 | + # Add a marker interface to set permissions for this kind |
648 | + # of distribution. |
649 | + if self == getUtility(ILaunchpadCelebrities).ubuntu: |
650 | + alsoProvides(self, IBaseDistribution) |
651 | + else: |
652 | + alsoProvides(self, IDerivativeDistribution) |
653 | + |
654 | + |
655 | @property |
656 | def uploaders(self): |
657 | """See `IDistribution`.""" |
658 | @@ -291,7 +304,7 @@ |
659 | @property |
660 | def full_functionality(self): |
661 | """See `IDistribution`.""" |
662 | - if self == getUtility(ILaunchpadCelebrities).ubuntu: |
663 | + if IBaseDistribution.providedBy(self): |
664 | return True |
665 | return False |
666 | |
667 | @@ -1488,7 +1501,7 @@ |
668 | def newSeries(self, name, displayname, title, summary, |
669 | description, version, parent_series, owner): |
670 | """See `IDistribution`.""" |
671 | - return DistroSeries( |
672 | + series = DistroSeries( |
673 | distribution=self, |
674 | name=name, |
675 | displayname=displayname, |
676 | @@ -1499,6 +1512,10 @@ |
677 | status=DistroSeriesStatus.EXPERIMENTAL, |
678 | parent_series=parent_series, |
679 | owner=owner) |
680 | + if owner.inTeam(self.driver) and not owner.inTeam(self.owner): |
681 | + # This driver is a release manager. |
682 | + series.driver = owner |
683 | + return series |
684 | |
685 | @property |
686 | def has_published_binaries(self): |
687 | |
688 | === modified file 'lib/lp/registry/model/product.py' |
689 | --- lib/lp/registry/model/product.py 2009-07-19 04:41:14 +0000 |
690 | +++ lib/lp/registry/model/product.py 2009-08-10 04:11:02 +0000 |
691 | @@ -897,8 +897,14 @@ |
692 | # XXX: jamesh 2008-04-11 |
693 | # Set the ID of the new ProductSeries to avoid flush order |
694 | # loops in ProductSet.createProduct() |
695 | - return ProductSeries(productID=self.id, owner=owner, name=name, |
696 | + series = ProductSeries(productID=self.id, owner=owner, name=name, |
697 | summary=summary, branch=branch) |
698 | + if owner.inTeam(self.driver) and not owner.inTeam(self.owner): |
699 | + # The user is a product driver, and should be the driver of this |
700 | + # series to make him the release manager. |
701 | + series.driver = owner |
702 | + return series |
703 | + |
704 | |
705 | def getRelease(self, version): |
706 | """See `IProduct`.""" |
707 | |
708 | === renamed file 'lib/lp/soyuz/stories/soyuz/xx-distroseries-edit.txt' => 'lib/lp/registry/stories/distroseries/xx-distroseries-edit.txt' |
This is my branch to enable distro drivers to manage their series. ribution to allow us to set separate permission on instances
It introduces two marker interfaces IBaseDistribution and
IDerivativeDist
of Distribution.
lp:~sinzui/launchpad/distro-driver /bugs.launchpad .net/bugs/ distro- driver *("milestone- views|distroser ies-views| xx-distroseries -edit)' \ *doc/(distroser ies|productseri es)" implementation: flacoste
Diff size: 708
Launchpad bug: https:/
Test command: ./bin/test -vv \
-t "registry.
-t "registry.
Pre-
Target release: 2.2.8
= Enable distro drivers to manage their series =
Distro drivers for needs the same privileges as project drivers to do
their job, but they cannot because some aspects of creating a distro
series require lots of back end preparation. This problem only affects
Ubuntu because it uses Soyuz and Translations, so the limitation
should only affect Ubuntu.
Assuming that Ubuntu is an exception then, distro drivers need the power
to create a series and doing so make them the series release manager. The
release manager can create and modify milestones.
== Rules ==
Updated the permission rules of distroseries and milestones.
* A distro.driver may create a series and he is automatically
made the series.driver (AKA release manager)
* Requires a new security permission
* The series.driver may create milestones and modify milestones
* requires an update to the existing permission
The Ubuntu exception is difficult in the case of adding a series because
security.py permissions are based on object type, not instances. and the
permission exception is only in regards to creating a series. The solution
suggest by Francis is to add marker interfaces based on the instance during
the SQLObject __init() phase. This is the same solution used by Archive.
ADDENDUM
When a product driver creates a series, he should be the series driver too.
A derivative distribution series must allow the driver to set the status.
== QA ==
* Ask the owner of Baltix to create the series he needs.
* Ask the owner of Baltix to update his two current milestones by
assigning them to one of his series
* Ask the owner of Baltix to create a milestone.
== Lint ==
Checking for conflicts. and issues in doctests and templates.
Running jslint, xmllint, pyflakes, and pylint.
Using normal rules.
Linting changed files: /launchpad/ security. py registry/ configure. zcml registry/ browser/ configure. zcml registry/ browser/ distroseries. py registry/ browser/ product. py registry/ browser/ tests/distroser ies-views. txt registry/ browser/ tests/milestone -views. txt registry/ doc/distroserie s.txt registry/ doc/productseri es.txt registry/ interfaces/ distribution. py registry/ model/distribut ion.py registry/ model/product. py
lib/canonical
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
lib/lp/
== Test ==
* lib/lp/ registry/ browser/ tests/distroser ies-views. txt ribution drivers can
IBaseDistribut ion drivers cannot. registry/ brow...
* Added tests to verify that IDerivativeDist
create series and edit the series that they are drivers of.
* lib/lp/