Merge ~andrey-fedoseev/launchpad:packaging-security into launchpad:master
- Git
- lp:~andrey-fedoseev/launchpad
- packaging-security
- Merge into master
Proposed by
Andrey Fedoseev
Status: | Merged |
---|---|
Approved by: | Andrey Fedoseev |
Approved revision: | 39957cba69557efc4a5fc74b07e152d893350dc6 |
Merge reported by: | Otto Co-Pilot |
Merged at revision: | not available |
Proposed branch: | ~andrey-fedoseev/launchpad:packaging-security |
Merge into: | launchpad:master |
Diff against target: |
1676 lines (+415/-392) 26 files modified
lib/lp/registry/browser/configure.zcml (+2/-2) lib/lp/registry/browser/productseries.py (+1/-1) lib/lp/registry/browser/sourcepackage.py (+23/-15) lib/lp/registry/browser/tests/packaging-views.rst (+2/-2) lib/lp/registry/browser/tests/sourcepackage-views.rst (+16/-11) lib/lp/registry/browser/tests/test_packaging.py (+44/-20) lib/lp/registry/browser/tests/test_product.py (+3/-1) lib/lp/registry/browser/tests/test_sourcepackage_views.py (+97/-60) lib/lp/registry/configure.zcml (+4/-2) lib/lp/registry/interfaces/packaging.py (+3/-8) lib/lp/registry/interfaces/productseries.py (+5/-5) lib/lp/registry/interfaces/sourcepackage.py (+15/-26) lib/lp/registry/model/packaging.py (+18/-39) lib/lp/registry/model/productseries.py (+4/-4) lib/lp/registry/model/sourcepackage.py (+5/-10) lib/lp/registry/security.py (+7/-1) lib/lp/registry/stories/packaging/xx-distributionsourcepackage-packaging-concurrent-deletion.rst (+3/-3) lib/lp/registry/stories/packaging/xx-distributionsourcepackage-packaging.rst (+8/-8) lib/lp/registry/stories/packaging/xx-sourcepackage-packaging.rst (+47/-74) lib/lp/registry/stories/product/xx-product-package-pages.rst (+5/-3) lib/lp/registry/tests/test_distroseries.py (+1/-1) lib/lp/registry/tests/test_packaging.py (+62/-23) lib/lp/registry/tests/test_productseries.py (+1/-2) lib/lp/registry/tests/test_sourcepackage.py (+34/-69) lib/lp/soyuz/tests/test_publishing.py (+3/-1) lib/lp/testing/factory.py (+2/-1) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Colin Watson (community) | Approve | ||
Review via email: mp+429758@code.launchpad.net |
Commit message
New security model for `Packaging`
Description of the change
Only the product owners, package maintainers, admins and registry experts are now allowed to create, edit or remove package links
- {IProductSeries
- `launchpad.Edit` on `IPackaging`: True if user has `launchpad.Edit` on either corresponding `IProductSeries` or `ISourcePackage`
- /+ubuntupkg on `IProductSeries` is now protected with `launchpad.Edit`
- /+edit-packaging on `ISourcePackage` is now protected with `launchpad.Edit`
- /+remove-packaging on `ISourcePackage` is protected with `launchpad.Edit` applied to `sourcepackage.
- all tests are updated accordingly
To post a comment you must log in.
Revision history for this message
Colin Watson (cjwatson) : | # |
review:
Approve
Revision history for this message
Andrey Fedoseev (andrey-fedoseev) : | # |
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/lib/lp/registry/browser/configure.zcml b/lib/lp/registry/browser/configure.zcml | |||
2 | index 6f041d4..b90507b 100644 | |||
3 | --- a/lib/lp/registry/browser/configure.zcml | |||
4 | +++ b/lib/lp/registry/browser/configure.zcml | |||
5 | @@ -2022,7 +2022,7 @@ | |||
6 | 2022 | name="+ubuntupkg" | 2022 | name="+ubuntupkg" |
7 | 2023 | for="lp.registry.interfaces.productseries.IProductSeries" | 2023 | for="lp.registry.interfaces.productseries.IProductSeries" |
8 | 2024 | class="lp.registry.browser.productseries.ProductSeriesUbuntuPackagingView" | 2024 | class="lp.registry.browser.productseries.ProductSeriesUbuntuPackagingView" |
10 | 2025 | permission="launchpad.AnyPerson" | 2025 | permission="launchpad.Edit" |
11 | 2026 | template="../templates/productseries-ubuntupkg.pt" | 2026 | template="../templates/productseries-ubuntupkg.pt" |
12 | 2027 | /> | 2027 | /> |
13 | 2028 | <browser:pages | 2028 | <browser:pages |
14 | @@ -2621,7 +2621,7 @@ | |||
15 | 2621 | name="+edit-packaging" | 2621 | name="+edit-packaging" |
16 | 2622 | for="lp.registry.interfaces.sourcepackage.ISourcePackage" | 2622 | for="lp.registry.interfaces.sourcepackage.ISourcePackage" |
17 | 2623 | class="lp.registry.browser.sourcepackage.SourcePackageChangeUpstreamView" | 2623 | class="lp.registry.browser.sourcepackage.SourcePackageChangeUpstreamView" |
19 | 2624 | permission="launchpad.AnyPerson" | 2624 | permission="launchpad.Edit" |
20 | 2625 | template="../templates/sourcepackage-edit-packaging.pt" | 2625 | template="../templates/sourcepackage-edit-packaging.pt" |
21 | 2626 | /> | 2626 | /> |
22 | 2627 | <browser:page | 2627 | <browser:page |
23 | diff --git a/lib/lp/registry/browser/productseries.py b/lib/lp/registry/browser/productseries.py | |||
24 | index 8759e95..9b05317 100644 | |||
25 | --- a/lib/lp/registry/browser/productseries.py | |||
26 | +++ b/lib/lp/registry/browser/productseries.py | |||
27 | @@ -278,7 +278,7 @@ class ProductSeriesOverviewMenu( | |||
28 | 278 | summary = "Change the branch for this series" | 278 | summary = "Change the branch for this series" |
29 | 279 | return Link("+setbranch", text, summary, icon=icon) | 279 | return Link("+setbranch", text, summary, icon=icon) |
30 | 280 | 280 | ||
32 | 281 | @enabled_with_permission("launchpad.AnyPerson") | 281 | @enabled_with_permission("launchpad.Edit") |
33 | 282 | def ubuntupkg(self): | 282 | def ubuntupkg(self): |
34 | 283 | """Return a link to link this series to an ubuntu sourcepackage.""" | 283 | """Return a link to link this series to an ubuntu sourcepackage.""" |
35 | 284 | text = "Link to Ubuntu package" | 284 | text = "Link to Ubuntu package" |
36 | diff --git a/lib/lp/registry/browser/sourcepackage.py b/lib/lp/registry/browser/sourcepackage.py | |||
37 | index 3230cf4..461a963 100644 | |||
38 | --- a/lib/lp/registry/browser/sourcepackage.py | |||
39 | +++ b/lib/lp/registry/browser/sourcepackage.py | |||
40 | @@ -34,6 +34,7 @@ from zope.schema.vocabulary import ( | |||
41 | 34 | SimpleVocabulary, | 34 | SimpleVocabulary, |
42 | 35 | getVocabularyRegistry, | 35 | getVocabularyRegistry, |
43 | 36 | ) | 36 | ) |
44 | 37 | from zope.security.interfaces import Unauthorized | ||
45 | 37 | 38 | ||
46 | 38 | from lp import _ | 39 | from lp import _ |
47 | 39 | from lp.app.browser.launchpadform import ( | 40 | from lp.app.browser.launchpadform import ( |
48 | @@ -61,9 +62,11 @@ from lp.services.webapp import ( | |||
49 | 61 | canonical_url, | 62 | canonical_url, |
50 | 62 | stepto, | 63 | stepto, |
51 | 63 | ) | 64 | ) |
52 | 65 | from lp.services.webapp.authorization import check_permission | ||
53 | 64 | from lp.services.webapp.breadcrumb import Breadcrumb | 66 | from lp.services.webapp.breadcrumb import Breadcrumb |
54 | 65 | from lp.services.webapp.escaping import structured | 67 | from lp.services.webapp.escaping import structured |
55 | 66 | from lp.services.webapp.interfaces import IBreadcrumb | 68 | from lp.services.webapp.interfaces import IBreadcrumb |
56 | 69 | from lp.services.webapp.menu import enabled_with_permission | ||
57 | 67 | from lp.services.webapp.publisher import LaunchpadView | 70 | from lp.services.webapp.publisher import LaunchpadView |
58 | 68 | from lp.services.worlddata.helpers import browser_languages | 71 | from lp.services.worlddata.helpers import browser_languages |
59 | 69 | from lp.services.worlddata.interfaces.country import ICountry | 72 | from lp.services.worlddata.interfaces.country import ICountry |
60 | @@ -214,13 +217,9 @@ class SourcePackageOverviewMenu(ApplicationMenu): | |||
61 | 214 | def copyright(self): | 217 | def copyright(self): |
62 | 215 | return Link("+copyright", "View copyright", icon="info") | 218 | return Link("+copyright", "View copyright", icon="info") |
63 | 216 | 219 | ||
64 | 220 | @enabled_with_permission("launchpad.Edit") | ||
65 | 217 | def edit_packaging(self): | 221 | def edit_packaging(self): |
72 | 218 | return Link( | 222 | return Link("+edit-packaging", "Change upstream link", icon="edit") |
67 | 219 | "+edit-packaging", | ||
68 | 220 | "Change upstream link", | ||
69 | 221 | icon="edit", | ||
70 | 222 | enabled=self.userCanDeletePackaging(), | ||
71 | 223 | ) | ||
73 | 224 | 223 | ||
74 | 225 | def remove_packaging(self): | 224 | def remove_packaging(self): |
75 | 226 | return Link( | 225 | return Link( |
76 | @@ -230,13 +229,9 @@ class SourcePackageOverviewMenu(ApplicationMenu): | |||
77 | 230 | enabled=self.userCanDeletePackaging(), | 229 | enabled=self.userCanDeletePackaging(), |
78 | 231 | ) | 230 | ) |
79 | 232 | 231 | ||
80 | 232 | @enabled_with_permission("launchpad.Edit") | ||
81 | 233 | def set_upstream(self): | 233 | def set_upstream(self): |
88 | 234 | return Link( | 234 | return Link("+edit-packaging", "Set upstream link", icon="add") |
83 | 235 | "+edit-packaging", | ||
84 | 236 | "Set upstream link", | ||
85 | 237 | icon="add", | ||
86 | 238 | enabled=self.userCanDeletePackaging(), | ||
87 | 239 | ) | ||
89 | 240 | 235 | ||
90 | 241 | def builds(self): | 236 | def builds(self): |
91 | 242 | text = "Show builds" | 237 | text = "Show builds" |
92 | @@ -245,8 +240,8 @@ class SourcePackageOverviewMenu(ApplicationMenu): | |||
93 | 245 | def userCanDeletePackaging(self): | 240 | def userCanDeletePackaging(self): |
94 | 246 | packaging = self.context.direct_packaging | 241 | packaging = self.context.direct_packaging |
95 | 247 | if packaging is None: | 242 | if packaging is None: |
98 | 248 | return True | 243 | return False |
99 | 249 | return packaging.userCanDelete() | 244 | return check_permission("launchpad.Edit", packaging) |
100 | 250 | 245 | ||
101 | 251 | 246 | ||
102 | 252 | class SourcePackageChangeUpstreamStepOne(ReturnToReferrerMixin, StepView): | 247 | class SourcePackageChangeUpstreamStepOne(ReturnToReferrerMixin, StepView): |
103 | @@ -425,10 +420,23 @@ class SourcePackageRemoveUpstreamView( | |||
104 | 425 | label = "Unlink an upstream project" | 420 | label = "Unlink an upstream project" |
105 | 426 | page_title = label | 421 | page_title = label |
106 | 427 | 422 | ||
107 | 423 | def initialize(self): | ||
108 | 424 | self.packaging = self.context.direct_packaging | ||
109 | 425 | if self.packaging is not None: | ||
110 | 426 | # We do permission check here rather than protecting the view | ||
111 | 427 | # in ZCML because product owners should also be allowed to unlink | ||
112 | 428 | # projects, even if they don't have edit permission on the source | ||
113 | 429 | # package. | ||
114 | 430 | if not check_permission("launchpad.Edit", self.packaging): | ||
115 | 431 | raise Unauthorized( | ||
116 | 432 | "You are not allowed to unlink an upstream project" | ||
117 | 433 | ) | ||
118 | 434 | super().initialize() | ||
119 | 435 | |||
120 | 428 | @action("Unlink") | 436 | @action("Unlink") |
121 | 429 | def unlink(self, action, data): | 437 | def unlink(self, action, data): |
122 | 430 | old_series = self.context.productseries | 438 | old_series = self.context.productseries |
124 | 431 | if self.context.direct_packaging is not None: | 439 | if self.packaging is not None: |
125 | 432 | getUtility(IPackagingUtil).deletePackaging( | 440 | getUtility(IPackagingUtil).deletePackaging( |
126 | 433 | self.context.productseries, | 441 | self.context.productseries, |
127 | 434 | self.context.sourcepackagename, | 442 | self.context.sourcepackagename, |
128 | diff --git a/lib/lp/registry/browser/tests/packaging-views.rst b/lib/lp/registry/browser/tests/packaging-views.rst | |||
129 | index 3610369..260ec7f 100644 | |||
130 | --- a/lib/lp/registry/browser/tests/packaging-views.rst | |||
131 | +++ b/lib/lp/registry/browser/tests/packaging-views.rst | |||
132 | @@ -30,6 +30,7 @@ The view has a label and requires a distro series and a source package name. | |||
133 | 30 | The distroseries field's vocabulary is the same as the ubuntu.series | 30 | The distroseries field's vocabulary is the same as the ubuntu.series |
134 | 31 | attribute. | 31 | attribute. |
135 | 32 | 32 | ||
136 | 33 | >>> _ = login_person(product.owner) | ||
137 | 33 | >>> view = create_view(productseries, "+ubuntupkg") | 34 | >>> view = create_view(productseries, "+ubuntupkg") |
138 | 34 | >>> print(view.label) | 35 | >>> print(view.label) |
139 | 35 | Ubuntu source packaging | 36 | Ubuntu source packaging |
140 | @@ -200,8 +201,7 @@ and a new entry can be added to the packaging history. | |||
141 | 200 | ... ) | 201 | ... ) |
142 | 201 | >>> grumpy_series.status = SeriesStatus.FROZEN | 202 | >>> grumpy_series.status = SeriesStatus.FROZEN |
143 | 202 | 203 | ||
146 | 203 | >>> a_user = factory.makePerson(name="hedgehog") | 204 | >>> _ = login_person(product.owner) |
145 | 204 | >>> ignored = login_person(a_user) | ||
147 | 205 | >>> form = { | 205 | >>> form = { |
148 | 206 | ... "field.sourcepackagename": "hot", | 206 | ... "field.sourcepackagename": "hot", |
149 | 207 | ... "field.actions.continue": "Update", | 207 | ... "field.actions.continue": "Update", |
150 | diff --git a/lib/lp/registry/browser/tests/sourcepackage-views.rst b/lib/lp/registry/browser/tests/sourcepackage-views.rst | |||
151 | index b759ece..32a971a 100644 | |||
152 | --- a/lib/lp/registry/browser/tests/sourcepackage-views.rst | |||
153 | +++ b/lib/lp/registry/browser/tests/sourcepackage-views.rst | |||
154 | @@ -8,8 +8,9 @@ Edit packaging view | |||
155 | 8 | >>> productseries = factory.makeProductSeries( | 8 | >>> productseries = factory.makeProductSeries( |
156 | 9 | ... name="crazy", product=product | 9 | ... name="crazy", product=product |
157 | 10 | ... ) | 10 | ... ) |
158 | 11 | >>> distro_owner = factory.makePerson() | ||
159 | 11 | >>> distribution = factory.makeDistribution( | 12 | >>> distribution = factory.makeDistribution( |
161 | 12 | ... name="youbuntu", displayname="Youbuntu" | 13 | ... name="youbuntu", displayname="Youbuntu", owner=distro_owner |
162 | 13 | ... ) | 14 | ... ) |
163 | 14 | >>> distroseries = factory.makeDistroSeries( | 15 | >>> distroseries = factory.makeDistroSeries( |
164 | 15 | ... name="busy", distribution=distribution | 16 | ... name="busy", distribution=distribution |
165 | @@ -53,7 +54,7 @@ This is a multistep view. In the first step, the product is specified. | |||
166 | 53 | >>> print(view.view.request.form) | 54 | >>> print(view.view.request.form) |
167 | 54 | {'field.__visited_steps__': 'sourcepackage_change_upstream_step1'} | 55 | {'field.__visited_steps__': 'sourcepackage_change_upstream_step1'} |
168 | 55 | 56 | ||
170 | 56 | >>> ignored = login_person(product.owner) | 57 | >>> _ = login_person(distro_owner) |
171 | 57 | >>> form = { | 58 | >>> form = { |
172 | 58 | ... "field.product": "bonkers", | 59 | ... "field.product": "bonkers", |
173 | 59 | ... "field.actions.continue": "Continue", | 60 | ... "field.actions.continue": "Continue", |
174 | @@ -63,7 +64,7 @@ This is a multistep view. In the first step, the product is specified. | |||
175 | 63 | ... package, | 64 | ... package, |
176 | 64 | ... name="+edit-packaging", | 65 | ... name="+edit-packaging", |
177 | 65 | ... form=form, | 66 | ... form=form, |
179 | 66 | ... principal=product.owner, | 67 | ... principal=distro_owner, |
180 | 67 | ... ) | 68 | ... ) |
181 | 68 | >>> view.view.errors | 69 | >>> view.view.errors |
182 | 69 | [] | 70 | [] |
183 | @@ -88,7 +89,7 @@ product can be chosen from a list of options. | |||
184 | 88 | ... package, | 89 | ... package, |
185 | 89 | ... name="+edit-packaging", | 90 | ... name="+edit-packaging", |
186 | 90 | ... form=form, | 91 | ... form=form, |
188 | 91 | ... principal=product.owner, | 92 | ... principal=distro_owner, |
189 | 92 | ... ) | 93 | ... ) |
190 | 93 | 94 | ||
191 | 94 | >>> ignored = view.view.render() | 95 | >>> ignored = view.view.render() |
192 | @@ -124,7 +125,7 @@ then the current product series will be the selected option. | |||
193 | 124 | ... package, | 125 | ... package, |
194 | 125 | ... name="+edit-packaging", | 126 | ... name="+edit-packaging", |
195 | 126 | ... form=form, | 127 | ... form=form, |
197 | 127 | ... principal=product.owner, | 128 | ... principal=distro_owner, |
198 | 128 | ... ) | 129 | ... ) |
199 | 129 | >>> print(view.view.widgets.get("productseries")._getFormValue().name) | 130 | >>> print(view.view.widgets.get("productseries")._getFormValue().name) |
200 | 130 | crazy | 131 | crazy |
201 | @@ -141,7 +142,7 @@ empty. | |||
202 | 141 | ... package, | 142 | ... package, |
203 | 142 | ... name="+edit-packaging", | 143 | ... name="+edit-packaging", |
204 | 143 | ... form=form, | 144 | ... form=form, |
206 | 144 | ... principal=product.owner, | 145 | ... principal=distro_owner, |
207 | 145 | ... ) | 146 | ... ) |
208 | 146 | >>> for error in view.view.errors: | 147 | >>> for error in view.view.errors: |
209 | 147 | ... print(pretty(error.args)) | 148 | ... print(pretty(error.args)) |
210 | @@ -161,7 +162,7 @@ but there is no notification message that the upstream link was updated. | |||
211 | 161 | ... package, | 162 | ... package, |
212 | 162 | ... name="+edit-packaging", | 163 | ... name="+edit-packaging", |
213 | 163 | ... form=form, | 164 | ... form=form, |
215 | 164 | ... principal=product.owner, | 165 | ... principal=distro_owner, |
216 | 165 | ... ) | 166 | ... ) |
217 | 166 | >>> print(view.view) | 167 | >>> print(view.view) |
218 | 167 | <...SourcePackageChangeUpstreamStepTwo object...> | 168 | <...SourcePackageChangeUpstreamStepTwo object...> |
219 | @@ -382,11 +383,12 @@ to the project series. | |||
220 | 382 | >>> print(view.cancel_url) | 383 | >>> print(view.cancel_url) |
221 | 383 | http://launchpad.test/youbuntu/wonky/+source/stinkypackage | 384 | http://launchpad.test/youbuntu/wonky/+source/stinkypackage |
222 | 384 | 385 | ||
223 | 385 | >>> user = package.packaging.owner | ||
224 | 386 | >>> ignored = login_person(user) | ||
225 | 387 | >>> form = {"field.actions.unlink": "Unlink"} | 386 | >>> form = {"field.actions.unlink": "Unlink"} |
226 | 388 | >>> view = create_initialized_view( | 387 | >>> view = create_initialized_view( |
228 | 389 | ... package, name="+remove-packaging", form=form, principal=user | 388 | ... package, |
229 | 389 | ... name="+remove-packaging", | ||
230 | 390 | ... form=form, | ||
231 | 391 | ... principal=distro_owner, | ||
232 | 390 | ... ) | 392 | ... ) |
233 | 391 | >>> view.errors | 393 | >>> view.errors |
234 | 392 | [] | 394 | [] |
235 | @@ -401,7 +403,10 @@ they get a message telling them that the link has already been | |||
236 | 401 | deleted. | 403 | deleted. |
237 | 402 | 404 | ||
238 | 403 | >>> view = create_initialized_view( | 405 | >>> view = create_initialized_view( |
240 | 404 | ... package, name="+remove-packaging", form=form, principal=user | 406 | ... package, |
241 | 407 | ... name="+remove-packaging", | ||
242 | 408 | ... form=form, | ||
243 | 409 | ... principal=distro_owner, | ||
244 | 405 | ... ) | 410 | ... ) |
245 | 406 | >>> view.errors | 411 | >>> view.errors |
246 | 407 | [] | 412 | [] |
247 | diff --git a/lib/lp/registry/browser/tests/test_packaging.py b/lib/lp/registry/browser/tests/test_packaging.py | |||
248 | index 7549a51..d45577a 100644 | |||
249 | --- a/lib/lp/registry/browser/tests/test_packaging.py | |||
250 | +++ b/lib/lp/registry/browser/tests/test_packaging.py | |||
251 | @@ -12,7 +12,7 @@ from lp.registry.interfaces.packaging import IPackagingUtil, PackagingType | |||
252 | 12 | from lp.registry.interfaces.product import IProductSet | 12 | from lp.registry.interfaces.product import IProductSet |
253 | 13 | from lp.registry.interfaces.sourcepackagename import ISourcePackageNameSet | 13 | from lp.registry.interfaces.sourcepackagename import ISourcePackageNameSet |
254 | 14 | from lp.services.features.testing import FeatureFixture | 14 | from lp.services.features.testing import FeatureFixture |
256 | 15 | from lp.testing import TestCaseWithFactory, login, logout | 15 | from lp.testing import TestCaseWithFactory, login, logout, person_logged_in |
257 | 16 | from lp.testing.layers import DatabaseFunctionalLayer | 16 | from lp.testing.layers import DatabaseFunctionalLayer |
258 | 17 | from lp.testing.pages import setupBrowser | 17 | from lp.testing.pages import setupBrowser |
259 | 18 | from lp.testing.views import create_initialized_view | 18 | from lp.testing.views import create_initialized_view |
260 | @@ -49,7 +49,7 @@ class TestProductSeriesUbuntuPackagingView(WithScenarios, TestCaseWithFactory): | |||
261 | 49 | ) | 49 | ) |
262 | 50 | self.packaging_util = getUtility(IPackagingUtil) | 50 | self.packaging_util = getUtility(IPackagingUtil) |
263 | 51 | 51 | ||
265 | 52 | def test_no_error_when_trying_to_readd_same_package(self): | 52 | def test_no_error_when_trying_to_re_add_same_package(self): |
266 | 53 | # There is no reason to display an error when the user's action | 53 | # There is no reason to display an error when the user's action |
267 | 54 | # wouldn't cause a state change. | 54 | # wouldn't cause a state change. |
268 | 55 | self.packaging_util.createPackaging( | 55 | self.packaging_util.createPackaging( |
269 | @@ -65,9 +65,13 @@ class TestProductSeriesUbuntuPackagingView(WithScenarios, TestCaseWithFactory): | |||
270 | 65 | "field.sourcepackagename": self.sourcepackagename.name, | 65 | "field.sourcepackagename": self.sourcepackagename.name, |
271 | 66 | "field.actions.continue": "Continue", | 66 | "field.actions.continue": "Continue", |
272 | 67 | } | 67 | } |
276 | 68 | view = create_initialized_view( | 68 | with person_logged_in(self.product.owner): |
277 | 69 | self.productseries, "+ubuntupkg", form=form | 69 | view = create_initialized_view( |
278 | 70 | ) | 70 | self.productseries, |
279 | 71 | "+ubuntupkg", | ||
280 | 72 | form=form, | ||
281 | 73 | principal=self.product.owner, | ||
282 | 74 | ) | ||
283 | 71 | self.assertEqual([], view.errors) | 75 | self.assertEqual([], view.errors) |
284 | 72 | 76 | ||
285 | 73 | def test_cannot_link_to_linked_package(self): | 77 | def test_cannot_link_to_linked_package(self): |
286 | @@ -78,9 +82,13 @@ class TestProductSeriesUbuntuPackagingView(WithScenarios, TestCaseWithFactory): | |||
287 | 78 | "field.sourcepackagename": "hot", | 82 | "field.sourcepackagename": "hot", |
288 | 79 | "field.actions.continue": "Continue", | 83 | "field.actions.continue": "Continue", |
289 | 80 | } | 84 | } |
293 | 81 | view = create_initialized_view( | 85 | with person_logged_in(self.product.owner): |
294 | 82 | self.productseries, "+ubuntupkg", form=form | 86 | view = create_initialized_view( |
295 | 83 | ) | 87 | self.productseries, |
296 | 88 | "+ubuntupkg", | ||
297 | 89 | form=form, | ||
298 | 90 | principal=self.product.owner, | ||
299 | 91 | ) | ||
300 | 84 | self.assertEqual([], view.errors) | 92 | self.assertEqual([], view.errors) |
301 | 85 | other_productseries = self.factory.makeProductSeries( | 93 | other_productseries = self.factory.makeProductSeries( |
302 | 86 | product=self.product, name="hottest" | 94 | product=self.product, name="hottest" |
303 | @@ -90,9 +98,13 @@ class TestProductSeriesUbuntuPackagingView(WithScenarios, TestCaseWithFactory): | |||
304 | 90 | "field.sourcepackagename": "hot", | 98 | "field.sourcepackagename": "hot", |
305 | 91 | "field.actions.continue": "Continue", | 99 | "field.actions.continue": "Continue", |
306 | 92 | } | 100 | } |
310 | 93 | view = create_initialized_view( | 101 | with person_logged_in(self.product.owner): |
311 | 94 | other_productseries, "+ubuntupkg", form=form | 102 | view = create_initialized_view( |
312 | 95 | ) | 103 | other_productseries, |
313 | 104 | "+ubuntupkg", | ||
314 | 105 | form=form, | ||
315 | 106 | principal=self.product.owner, | ||
316 | 107 | ) | ||
317 | 96 | view_errors = [ | 108 | view_errors = [ |
318 | 97 | 'The <a href="http://launchpad.test/ubuntu/hoary/+source/hot">' | 109 | 'The <a href="http://launchpad.test/ubuntu/hoary/+source/hot">' |
319 | 98 | "hot</a> package in Hoary is already linked to another series." | 110 | "hot</a> package in Hoary is already linked to another series." |
320 | @@ -106,9 +118,13 @@ class TestProductSeriesUbuntuPackagingView(WithScenarios, TestCaseWithFactory): | |||
321 | 106 | "field.sourcepackagename": "", | 118 | "field.sourcepackagename": "", |
322 | 107 | "field.actions.continue": "Continue", | 119 | "field.actions.continue": "Continue", |
323 | 108 | } | 120 | } |
327 | 109 | view = create_initialized_view( | 121 | with person_logged_in(self.product.owner): |
328 | 110 | self.productseries, "+ubuntupkg", form=form | 122 | view = create_initialized_view( |
329 | 111 | ) | 123 | self.productseries, |
330 | 124 | "+ubuntupkg", | ||
331 | 125 | form=form, | ||
332 | 126 | principal=self.product.owner, | ||
333 | 127 | ) | ||
334 | 112 | self.assertEqual(1, len(view.errors)) | 128 | self.assertEqual(1, len(view.errors)) |
335 | 113 | self.assertEqual("sourcepackagename", view.errors[0].field_name) | 129 | self.assertEqual("sourcepackagename", view.errors[0].field_name) |
336 | 114 | self.assertEqual("Required input is missing.", view.errors[0].doc()) | 130 | self.assertEqual("Required input is missing.", view.errors[0].doc()) |
337 | @@ -125,9 +141,13 @@ class TestProductSeriesUbuntuPackagingView(WithScenarios, TestCaseWithFactory): | |||
338 | 125 | "field.sourcepackagename": "vapor", | 141 | "field.sourcepackagename": "vapor", |
339 | 126 | "field.actions.continue": "Continue", | 142 | "field.actions.continue": "Continue", |
340 | 127 | } | 143 | } |
344 | 128 | view = create_initialized_view( | 144 | with person_logged_in(self.product.owner): |
345 | 129 | self.productseries, "+ubuntupkg", form=form | 145 | view = create_initialized_view( |
346 | 130 | ) | 146 | self.productseries, |
347 | 147 | "+ubuntupkg", | ||
348 | 148 | form=form, | ||
349 | 149 | principal=self.product.owner, | ||
350 | 150 | ) | ||
351 | 131 | view_errors = ["The source package is not published in Hoary."] | 151 | view_errors = ["The source package is not published in Hoary."] |
352 | 132 | self.assertEqual(view_errors, view.errors) | 152 | self.assertEqual(view_errors, view.errors) |
353 | 133 | 153 | ||
354 | @@ -142,9 +162,13 @@ class TestProductSeriesUbuntuPackagingView(WithScenarios, TestCaseWithFactory): | |||
355 | 142 | "field.sourcepackagename": "hot", | 162 | "field.sourcepackagename": "hot", |
356 | 143 | "field.actions.continue": "Continue", | 163 | "field.actions.continue": "Continue", |
357 | 144 | } | 164 | } |
361 | 145 | view = create_initialized_view( | 165 | with person_logged_in(self.product.owner): |
362 | 146 | self.productseries, "+ubuntupkg", form=form | 166 | view = create_initialized_view( |
363 | 147 | ) | 167 | self.productseries, |
364 | 168 | "+ubuntupkg", | ||
365 | 169 | form=form, | ||
366 | 170 | principal=self.product.owner, | ||
367 | 171 | ) | ||
368 | 148 | self.assertEqual([], view.errors) | 172 | self.assertEqual([], view.errors) |
369 | 149 | has_packaging = self.packaging_util.packagingEntryExists( | 173 | has_packaging = self.packaging_util.packagingEntryExists( |
370 | 150 | self.sourcepackagename, warty, self.productseries | 174 | self.sourcepackagename, warty, self.productseries |
371 | diff --git a/lib/lp/registry/browser/tests/test_product.py b/lib/lp/registry/browser/tests/test_product.py | |||
372 | index de8f154..61618f0 100644 | |||
373 | --- a/lib/lp/registry/browser/tests/test_product.py | |||
374 | +++ b/lib/lp/registry/browser/tests/test_product.py | |||
375 | @@ -862,7 +862,9 @@ class TestProductEditView(BrowserTestCase): | |||
376 | 862 | # It should be an error to make a Product private if it is packaged. | 862 | # It should be an error to make a Product private if it is packaged. |
377 | 863 | product = self.factory.makeProduct() | 863 | product = self.factory.makeProduct() |
378 | 864 | sourcepackage = self.factory.makeSourcePackage() | 864 | sourcepackage = self.factory.makeSourcePackage() |
380 | 865 | sourcepackage.setPackaging(product.development_focus, product.owner) | 865 | removeSecurityProxy(sourcepackage).setPackaging( |
381 | 866 | product.development_focus, product.owner | ||
382 | 867 | ) | ||
383 | 866 | browser = self.getViewBrowser(product, "+edit", user=product.owner) | 868 | browser = self.getViewBrowser(product, "+edit", user=product.owner) |
384 | 867 | info_type = browser.getControl(name="field.information_type") | 869 | info_type = browser.getControl(name="field.information_type") |
385 | 868 | info_type.value = ["PROPRIETARY"] | 870 | info_type.value = ["PROPRIETARY"] |
386 | diff --git a/lib/lp/registry/browser/tests/test_sourcepackage_views.py b/lib/lp/registry/browser/tests/test_sourcepackage_views.py | |||
387 | index 291c28a..8044c1a 100644 | |||
388 | --- a/lib/lp/registry/browser/tests/test_sourcepackage_views.py | |||
389 | +++ b/lib/lp/registry/browser/tests/test_sourcepackage_views.py | |||
390 | @@ -244,7 +244,7 @@ class TestSourcePackageUpstreamConnectionsView(TestCaseWithFactory): | |||
391 | 244 | distroseries=distroseries, | 244 | distroseries=distroseries, |
392 | 245 | version="1.5-0ubuntu1", | 245 | version="1.5-0ubuntu1", |
393 | 246 | ) | 246 | ) |
395 | 247 | self.source_package.setPackaging( | 247 | removeSecurityProxy(self.source_package).setPackaging( |
396 | 248 | productseries, productseries.product.owner | 248 | productseries, productseries.product.owner |
397 | 249 | ) | 249 | ) |
398 | 250 | 250 | ||
399 | @@ -301,80 +301,119 @@ class TestSourcePackagePackagingLinks(TestCaseWithFactory): | |||
400 | 301 | 301 | ||
401 | 302 | layer = DatabaseFunctionalLayer | 302 | layer = DatabaseFunctionalLayer |
402 | 303 | 303 | ||
413 | 304 | def makeSourcePackageOverviewMenu(self, with_packaging, karma=None): | 304 | def setUp(self, *args, **kwargs): |
414 | 305 | sourcepackage = self.factory.makeSourcePackage() | 305 | super().setUp(*args, **kwargs) |
415 | 306 | registrant = self.factory.makePerson() | 306 | self.sourcepackage = self.factory.makeSourcePackage() |
416 | 307 | if with_packaging: | 307 | self.maintainer = self.sourcepackage.distribution.owner |
417 | 308 | self.factory.makePackagingLink( | 308 | self.product_owner = self.factory.makePerson() |
418 | 309 | sourcepackagename=sourcepackage.sourcepackagename, | 309 | self.product = self.factory.makeProduct(owner=self.product_owner) |
419 | 310 | distroseries=sourcepackage.distroseries, | 310 | self.productseries = self.factory.makeProductSeries(self.product) |
420 | 311 | owner=registrant, | 311 | |
421 | 312 | ) | 312 | def makePackaging(self): |
422 | 313 | user = self.factory.makePerson(karma=karma) | 313 | self.factory.makePackagingLink( |
423 | 314 | sourcepackagename=self.sourcepackage.sourcepackagename, | ||
424 | 315 | distroseries=self.sourcepackage.distroseries, | ||
425 | 316 | productseries=self.productseries, | ||
426 | 317 | ) | ||
427 | 318 | |||
428 | 319 | def makeSourcePackageOverviewMenu(self, user): | ||
429 | 314 | with person_logged_in(user): | 320 | with person_logged_in(user): |
432 | 315 | menu = SourcePackageOverviewMenu(sourcepackage) | 321 | menu = SourcePackageOverviewMenu(self.sourcepackage) |
433 | 316 | return menu, user | 322 | return menu |
434 | 317 | 323 | ||
437 | 318 | def test_edit_packaging_link__enabled_without_packaging(self): | 324 | def test_edit_packaging_link__enabled_without_packaging_maintainer(self): |
438 | 319 | # If no packging exists, the edit_packaging link is always | 325 | # If no packaging exists, the edit_packaging link is always |
439 | 320 | # enabled. | 326 | # enabled. |
442 | 321 | menu, user = self.makeSourcePackageOverviewMenu(False, None) | 327 | menu = self.makeSourcePackageOverviewMenu(self.maintainer) |
443 | 322 | with person_logged_in(user): | 328 | with person_logged_in(self.maintainer): |
444 | 323 | self.assertTrue(menu.edit_packaging().enabled) | 329 | self.assertTrue(menu.edit_packaging().enabled) |
445 | 324 | 330 | ||
448 | 325 | def test_set_upstrem_link__enabled_without_packaging(self): | 331 | def test_set_upstream_link__enabled_without_packaging_maintainer(self): |
449 | 326 | # If no packging exists, the set_upstream link is always | 332 | # If no packaging exists, the set_upstream link is always |
450 | 327 | # enabled. | 333 | # enabled. |
453 | 328 | menu, user = self.makeSourcePackageOverviewMenu(False, None) | 334 | menu = self.makeSourcePackageOverviewMenu(self.maintainer) |
454 | 329 | with person_logged_in(user): | 335 | with person_logged_in(self.maintainer): |
455 | 330 | self.assertTrue(menu.set_upstream().enabled) | 336 | self.assertTrue(menu.set_upstream().enabled) |
456 | 331 | 337 | ||
463 | 332 | def test_remove_packaging_link__enabled_without_packaging(self): | 338 | def test_remove_packaging_link__enabled_without_packaging_maintainer(self): |
464 | 333 | # If no packging exists, the remove_packaging link is always | 339 | # If no packaging exists, the remove_packaging link is always |
465 | 334 | # enabled. | 340 | # disabled. |
466 | 335 | menu, user = self.makeSourcePackageOverviewMenu(False, None) | 341 | menu = self.makeSourcePackageOverviewMenu(self.maintainer) |
467 | 336 | with person_logged_in(user): | 342 | with person_logged_in(self.maintainer): |
468 | 337 | self.assertTrue(menu.remove_packaging().enabled) | 343 | self.assertFalse(menu.remove_packaging().enabled) |
469 | 338 | 344 | ||
475 | 339 | def test_edit_packaging_link__enabled_with_packaging_non_probation(self): | 345 | def test_edit_packaging_link__enabled_with_packaging_maintainer(self): |
476 | 340 | # If a packging exists, the edit_packaging link is enabled | 346 | # If a packaging exists, the edit_packaging link is enabled |
477 | 341 | # for the non-probationary users. | 347 | # for the package maintainer. |
478 | 342 | menu, user = self.makeSourcePackageOverviewMenu(True, 100) | 348 | self.makePackaging() |
479 | 343 | with person_logged_in(user): | 349 | menu = self.makeSourcePackageOverviewMenu(self.maintainer) |
480 | 350 | with person_logged_in(self.maintainer): | ||
481 | 344 | self.assertTrue(menu.edit_packaging().enabled) | 351 | self.assertTrue(menu.edit_packaging().enabled) |
482 | 345 | 352 | ||
488 | 346 | def test_set_upstrem_link__enabled_with_packaging_non_probation(self): | 353 | def test_set_upstream_link__enabled_with_packaging_maintainer(self): |
489 | 347 | # If a packging exists, the set_upstream link is enabled | 354 | # If a packaging exists, the set_upstream link is enabled |
490 | 348 | # for the non-probationary users. | 355 | # for the package maintainer. |
491 | 349 | menu, user = self.makeSourcePackageOverviewMenu(True, 100) | 356 | self.makePackaging() |
492 | 350 | with person_logged_in(user): | 357 | menu = self.makeSourcePackageOverviewMenu(self.maintainer) |
493 | 358 | with person_logged_in(self.maintainer): | ||
494 | 351 | self.assertTrue(menu.set_upstream().enabled) | 359 | self.assertTrue(menu.set_upstream().enabled) |
495 | 352 | 360 | ||
501 | 353 | def test_remove_packaging_link__enabled_with_packaging_non_probation(self): | 361 | def test_remove_packaging_link__enabled_with_packaging_maintainer(self): |
502 | 354 | # If a packging exists, the remove_packaging link is enabled | 362 | # If a packaging exists, the remove_packaging link is enabled |
503 | 355 | # for the non-probationary users. | 363 | # for the package maintainer. |
504 | 356 | menu, user = self.makeSourcePackageOverviewMenu(True, 100) | 364 | self.makePackaging() |
505 | 357 | with person_logged_in(user): | 365 | menu = self.makeSourcePackageOverviewMenu(self.maintainer) |
506 | 366 | with person_logged_in(self.maintainer): | ||
507 | 367 | self.assertTrue(menu.remove_packaging().enabled) | ||
508 | 368 | |||
509 | 369 | def test_edit_packaging_link__disabled_for_product_owner(self): | ||
510 | 370 | # If a packaging exists, the edit_packaging link is disabled | ||
511 | 371 | # for the product owner. | ||
512 | 372 | self.makePackaging() | ||
513 | 373 | menu = self.makeSourcePackageOverviewMenu(self.product_owner) | ||
514 | 374 | with person_logged_in(self.product_owner): | ||
515 | 375 | self.assertFalse(menu.edit_packaging().enabled) | ||
516 | 376 | |||
517 | 377 | def test_set_upstream_link__disabled_for_product_owner(self): | ||
518 | 378 | # If a packaging exists, the set_upstream link is disabled | ||
519 | 379 | # for the product owner. | ||
520 | 380 | self.makePackaging() | ||
521 | 381 | menu = self.makeSourcePackageOverviewMenu(self.product_owner) | ||
522 | 382 | with person_logged_in(self.product_owner): | ||
523 | 383 | self.assertFalse(menu.set_upstream().enabled) | ||
524 | 384 | |||
525 | 385 | def test_remove_packaging_link__enabled_for_product_owner(self): | ||
526 | 386 | # If a packaging exists, the remove_packaging link is enabled | ||
527 | 387 | # for product owner. | ||
528 | 388 | self.makePackaging() | ||
529 | 389 | menu = self.makeSourcePackageOverviewMenu(self.product_owner) | ||
530 | 390 | with person_logged_in(self.product_owner): | ||
531 | 358 | self.assertTrue(menu.remove_packaging().enabled) | 391 | self.assertTrue(menu.remove_packaging().enabled) |
532 | 359 | 392 | ||
537 | 360 | def test_edit_packaging_link__enabled_with_packaging_probation(self): | 393 | def test_edit_packaging_link__enabled_with_packaging_arbitrary(self): |
538 | 361 | # If a packging exists, the edit_packaging link is not enabled | 394 | # If a packaging exists, the edit_packaging link is not enabled |
539 | 362 | # for probationary users. | 395 | # for arbitrary users. |
540 | 363 | menu, user = self.makeSourcePackageOverviewMenu(True, None) | 396 | self.makePackaging() |
541 | 397 | user = self.factory.makePerson() | ||
542 | 398 | menu = self.makeSourcePackageOverviewMenu(user) | ||
543 | 364 | with person_logged_in(user): | 399 | with person_logged_in(user): |
544 | 365 | self.assertFalse(menu.edit_packaging().enabled) | 400 | self.assertFalse(menu.edit_packaging().enabled) |
545 | 366 | 401 | ||
550 | 367 | def test_set_upstrem_link__enabled_with_packaging_probation(self): | 402 | def test_set_upstream_link__enabled_with_packaging_arbitrary(self): |
551 | 368 | # If a packging exists, the set_upstream link is not enabled | 403 | # If a packaging exists, the set_upstream link is not enabled |
552 | 369 | # for probationary users. | 404 | # for arbitrary users. |
553 | 370 | menu, user = self.makeSourcePackageOverviewMenu(True, None) | 405 | self.makePackaging() |
554 | 406 | user = self.factory.makePerson() | ||
555 | 407 | menu = self.makeSourcePackageOverviewMenu(user) | ||
556 | 371 | with person_logged_in(user): | 408 | with person_logged_in(user): |
557 | 372 | self.assertFalse(menu.set_upstream().enabled) | 409 | self.assertFalse(menu.set_upstream().enabled) |
558 | 373 | 410 | ||
563 | 374 | def test_remove_packaging_link__enabled_with_packaging_probation(self): | 411 | def test_remove_packaging_link__enabled_with_packaging_arbitrary(self): |
564 | 375 | # If a packging exists, the remove_packaging link is not enabled | 412 | # If a packaging exists, the remove_packaging link is not enabled |
565 | 376 | # for probationary users. | 413 | # for arbitrary users. |
566 | 377 | menu, user = self.makeSourcePackageOverviewMenu(True, None) | 414 | self.makePackaging() |
567 | 415 | user = self.factory.makePerson() | ||
568 | 416 | menu = self.makeSourcePackageOverviewMenu(user) | ||
569 | 378 | with person_logged_in(user): | 417 | with person_logged_in(user): |
570 | 379 | self.assertFalse(menu.remove_packaging().enabled) | 418 | self.assertFalse(menu.remove_packaging().enabled) |
571 | 380 | 419 | ||
572 | @@ -392,10 +431,9 @@ class TestSourcePackageChangeUpstreamView(BrowserTestCase): | |||
573 | 392 | owner=product_owner, | 431 | owner=product_owner, |
574 | 393 | information_type=InformationType.PROPRIETARY, | 432 | information_type=InformationType.PROPRIETARY, |
575 | 394 | ) | 433 | ) |
578 | 395 | ubuntu_series = self.factory.makeUbuntuDistroSeries() | 434 | sp = self.factory.makeSourcePackage() |
577 | 396 | sp = self.factory.makeSourcePackage(distroseries=ubuntu_series) | ||
579 | 397 | browser = self.getViewBrowser( | 435 | browser = self.getViewBrowser( |
581 | 398 | sp, "+edit-packaging", user=product_owner | 436 | sp, "+edit-packaging", user=self.factory.makeAdministrator() |
582 | 399 | ) | 437 | ) |
583 | 400 | browser.getControl("Project").value = product_name | 438 | browser.getControl("Project").value = product_name |
584 | 401 | browser.getControl("Continue").click() | 439 | browser.getControl("Continue").click() |
585 | @@ -413,10 +451,9 @@ class TestSourcePackageChangeUpstreamView(BrowserTestCase): | |||
586 | 413 | ) | 451 | ) |
587 | 414 | series = self.factory.makeProductSeries(product=product) | 452 | series = self.factory.makeProductSeries(product=product) |
588 | 415 | series_displayname = series.displayname | 453 | series_displayname = series.displayname |
591 | 416 | ubuntu_series = self.factory.makeUbuntuDistroSeries() | 454 | sp = self.factory.makeSourcePackage() |
590 | 417 | sp = self.factory.makeSourcePackage(distroseries=ubuntu_series) | ||
592 | 418 | browser = self.getViewBrowser( | 455 | browser = self.getViewBrowser( |
594 | 419 | sp, "+edit-packaging", user=product_owner | 456 | sp, "+edit-packaging", user=self.factory.makeAdministrator() |
595 | 420 | ) | 457 | ) |
596 | 421 | browser.getControl("Project").value = product_name | 458 | browser.getControl("Project").value = product_name |
597 | 422 | browser.getControl("Continue").click() | 459 | browser.getControl("Continue").click() |
598 | diff --git a/lib/lp/registry/configure.zcml b/lib/lp/registry/configure.zcml | |||
599 | index f2d0433..585bbf1 100644 | |||
600 | --- a/lib/lp/registry/configure.zcml | |||
601 | +++ b/lib/lp/registry/configure.zcml | |||
602 | @@ -2188,8 +2188,10 @@ | |||
603 | 2188 | <allow | 2188 | <allow |
604 | 2189 | interface="lp.registry.interfaces.packaging.IPackaging"/> | 2189 | interface="lp.registry.interfaces.packaging.IPackaging"/> |
605 | 2190 | <require | 2190 | <require |
608 | 2191 | permission="zope.Public" | 2191 | permission="launchpad.Edit" |
609 | 2192 | set_schema="lp.registry.interfaces.packaging.IPackaging"/> | 2192 | set_schema="lp.registry.interfaces.packaging.IPackaging" |
610 | 2193 | attributes=" | ||
611 | 2194 | destroySelf" /> | ||
612 | 2193 | </class> | 2195 | </class> |
613 | 2194 | 2196 | ||
614 | 2195 | <!-- PackagingUtil --> | 2197 | <!-- PackagingUtil --> |
615 | diff --git a/lib/lp/registry/interfaces/packaging.py b/lib/lp/registry/interfaces/packaging.py | |||
616 | index 5ebddd5..ae16be5 100644 | |||
617 | --- a/lib/lp/registry/interfaces/packaging.py | |||
618 | +++ b/lib/lp/registry/interfaces/packaging.py | |||
619 | @@ -104,14 +104,6 @@ class IPackaging(IHasOwner): | |||
620 | 104 | ) | 104 | ) |
621 | 105 | ) | 105 | ) |
622 | 106 | 106 | ||
623 | 107 | def userCanDelete(): | ||
624 | 108 | """True, if the current user is allowed to delete this packaging, | ||
625 | 109 | else False. | ||
626 | 110 | |||
627 | 111 | Non-probationary users can delete packaging links that they believe | ||
628 | 112 | connect Ubuntu to bogus data. | ||
629 | 113 | """ | ||
630 | 114 | |||
631 | 115 | 107 | ||
632 | 116 | class IPackagingUtil(Interface): | 108 | class IPackagingUtil(Interface): |
633 | 117 | """Utilities to handle Packaging.""" | 109 | """Utilities to handle Packaging.""" |
634 | @@ -121,6 +113,9 @@ class IPackagingUtil(Interface): | |||
635 | 121 | ): | 113 | ): |
636 | 122 | """Create Packaging entry.""" | 114 | """Create Packaging entry.""" |
637 | 123 | 115 | ||
638 | 116 | def get(productseries, sourcepackagename, distroseries): | ||
639 | 117 | """Get Packaging entry.""" | ||
640 | 118 | |||
641 | 124 | def deletePackaging(productseries, sourcepackagename, distroseries): | 119 | def deletePackaging(productseries, sourcepackagename, distroseries): |
642 | 125 | """Delete a packaging entry.""" | 120 | """Delete a packaging entry.""" |
643 | 126 | 121 | ||
644 | diff --git a/lib/lp/registry/interfaces/productseries.py b/lib/lp/registry/interfaces/productseries.py | |||
645 | index 62e1ef5..f985de4 100644 | |||
646 | --- a/lib/lp/registry/interfaces/productseries.py | |||
647 | +++ b/lib/lp/registry/interfaces/productseries.py | |||
648 | @@ -102,6 +102,11 @@ class IProductSeriesEditRestricted(Interface): | |||
649 | 102 | def newMilestone(name, dateexpected=None, summary=None, code_name=None): | 102 | def newMilestone(name, dateexpected=None, summary=None, code_name=None): |
650 | 103 | """Create a new milestone for this ProjectSeries.""" | 103 | """Create a new milestone for this ProjectSeries.""" |
651 | 104 | 104 | ||
652 | 105 | def setPackaging(distroseries, sourcepackagename, owner): | ||
653 | 106 | """Create or update a Packaging record for this product series, | ||
654 | 107 | connecting it to the given distroseries and source package name. | ||
655 | 108 | """ | ||
656 | 109 | |||
657 | 105 | 110 | ||
658 | 106 | class IProductSeriesPublic(Interface): | 111 | class IProductSeriesPublic(Interface): |
659 | 107 | """Public IProductSeries properties.""" | 112 | """Public IProductSeries properties.""" |
660 | @@ -343,11 +348,6 @@ class IProductSeriesView( | |||
661 | 343 | """Return the SourcePackage that packages this project in Ubuntu's | 348 | """Return the SourcePackage that packages this project in Ubuntu's |
662 | 344 | translation focus or current series or any series, in that order.""" | 349 | translation focus or current series or any series, in that order.""" |
663 | 345 | 350 | ||
664 | 346 | def setPackaging(distroseries, sourcepackagename, owner): | ||
665 | 347 | """Create or update a Packaging record for this product series, | ||
666 | 348 | connecting it to the given distroseries and source package name. | ||
667 | 349 | """ | ||
668 | 350 | |||
669 | 351 | def getPackagingInDistribution(distribution): | 351 | def getPackagingInDistribution(distribution): |
670 | 352 | """Return all the Packaging entries for this product series for the | 352 | """Return all the Packaging entries for this product series for the |
671 | 353 | given distribution. Note that this only returns EXPLICT packaging | 353 | given distribution. Note that this only returns EXPLICT packaging |
672 | diff --git a/lib/lp/registry/interfaces/sourcepackage.py b/lib/lp/registry/interfaces/sourcepackage.py | |||
673 | index d2a4ad2..4cc038e 100644 | |||
674 | --- a/lib/lp/registry/interfaces/sourcepackage.py | |||
675 | +++ b/lib/lp/registry/interfaces/sourcepackage.py | |||
676 | @@ -224,32 +224,6 @@ class ISourcePackagePublic( | |||
677 | 224 | sourcepackagename compare not equal. | 224 | sourcepackagename compare not equal. |
678 | 225 | """ | 225 | """ |
679 | 226 | 226 | ||
680 | 227 | @operation_parameters(productseries=Reference(schema=IProductSeries)) | ||
681 | 228 | @call_with(owner=REQUEST_USER) | ||
682 | 229 | @export_write_operation() | ||
683 | 230 | @operation_for_version("devel") | ||
684 | 231 | def setPackaging(productseries, owner): | ||
685 | 232 | """Update the existing packaging record, or create a new packaging | ||
686 | 233 | record, that links the source package to the given productseries, | ||
687 | 234 | and record that it was done by the owner. | ||
688 | 235 | """ | ||
689 | 236 | |||
690 | 237 | @operation_parameters(productseries=Reference(schema=IProductSeries)) | ||
691 | 238 | @call_with(owner=REQUEST_USER) | ||
692 | 239 | @export_write_operation() | ||
693 | 240 | @operation_for_version("devel") | ||
694 | 241 | def setPackagingReturnSharingDetailPermissions(productseries, owner): | ||
695 | 242 | """Like setPackaging(), but returns getSharingDetailPermissions(). | ||
696 | 243 | |||
697 | 244 | This method is intended for AJAX usage on the +sharing-details | ||
698 | 245 | page. | ||
699 | 246 | """ | ||
700 | 247 | |||
701 | 248 | @export_write_operation() | ||
702 | 249 | @operation_for_version("devel") | ||
703 | 250 | def deletePackaging(): | ||
704 | 251 | """Delete the packaging for this sourcepackage.""" | ||
705 | 252 | |||
706 | 253 | def getSharingDetailPermissions(): | 227 | def getSharingDetailPermissions(): |
707 | 254 | """Return a dictionary of user permissions for +sharing-details page. | 228 | """Return a dictionary of user permissions for +sharing-details page. |
708 | 255 | 229 | ||
709 | @@ -364,6 +338,21 @@ class ISourcePackageEdit(Interface): | |||
710 | 364 | :return: None | 338 | :return: None |
711 | 365 | """ | 339 | """ |
712 | 366 | 340 | ||
713 | 341 | @operation_parameters(productseries=Reference(schema=IProductSeries)) | ||
714 | 342 | @call_with(owner=REQUEST_USER) | ||
715 | 343 | @export_write_operation() | ||
716 | 344 | @operation_for_version("devel") | ||
717 | 345 | def setPackaging(productseries, owner): | ||
718 | 346 | """Update the existing packaging record, or create a new packaging | ||
719 | 347 | record, that links the source package to the given productseries, | ||
720 | 348 | and record that it was done by the owner. | ||
721 | 349 | """ | ||
722 | 350 | |||
723 | 351 | @export_write_operation() | ||
724 | 352 | @operation_for_version("devel") | ||
725 | 353 | def deletePackaging(): | ||
726 | 354 | """Delete the packaging for this sourcepackage.""" | ||
727 | 355 | |||
728 | 367 | 356 | ||
729 | 368 | @exported_as_webservice_entry(as_of="beta") | 357 | @exported_as_webservice_entry(as_of="beta") |
730 | 369 | class ISourcePackage(ISourcePackagePublic, ISourcePackageEdit): | 358 | class ISourcePackage(ISourcePackagePublic, ISourcePackageEdit): |
731 | diff --git a/lib/lp/registry/model/packaging.py b/lib/lp/registry/model/packaging.py | |||
732 | index 2735a16..047b0e5 100644 | |||
733 | --- a/lib/lp/registry/model/packaging.py | |||
734 | +++ b/lib/lp/registry/model/packaging.py | |||
735 | @@ -7,10 +7,8 @@ from lazr.lifecycle.event import ObjectCreatedEvent, ObjectDeletedEvent | |||
736 | 7 | from zope.component import getUtility | 7 | from zope.component import getUtility |
737 | 8 | from zope.event import notify | 8 | from zope.event import notify |
738 | 9 | from zope.interface import implementer | 9 | from zope.interface import implementer |
739 | 10 | from zope.security.interfaces import Unauthorized | ||
740 | 11 | 10 | ||
741 | 12 | from lp.app.enums import InformationType | 11 | from lp.app.enums import InformationType |
742 | 13 | from lp.app.interfaces.launchpad import ILaunchpadCelebrities | ||
743 | 14 | from lp.registry.errors import CannotPackageProprietaryProduct | 12 | from lp.registry.errors import CannotPackageProprietaryProduct |
744 | 15 | from lp.registry.interfaces.packaging import ( | 13 | from lp.registry.interfaces.packaging import ( |
745 | 16 | IPackaging, | 14 | IPackaging, |
746 | @@ -23,7 +21,6 @@ from lp.services.database.datetimecol import UtcDateTimeCol | |||
747 | 23 | from lp.services.database.enumcol import DBEnum | 21 | from lp.services.database.enumcol import DBEnum |
748 | 24 | from lp.services.database.sqlbase import SQLBase | 22 | from lp.services.database.sqlbase import SQLBase |
749 | 25 | from lp.services.database.sqlobject import ForeignKey | 23 | from lp.services.database.sqlobject import ForeignKey |
750 | 26 | from lp.services.webapp.interfaces import ILaunchBag | ||
751 | 27 | 24 | ||
752 | 28 | 25 | ||
753 | 29 | @implementer(IPackaging) | 26 | @implementer(IPackaging) |
754 | @@ -66,29 +63,7 @@ class Packaging(SQLBase): | |||
755 | 66 | super().__init__(**kwargs) | 63 | super().__init__(**kwargs) |
756 | 67 | notify(ObjectCreatedEvent(self)) | 64 | notify(ObjectCreatedEvent(self)) |
757 | 68 | 65 | ||
758 | 69 | def userCanDelete(self): | ||
759 | 70 | """See `IPackaging`.""" | ||
760 | 71 | user = getUtility(ILaunchBag).user | ||
761 | 72 | if user is None: | ||
762 | 73 | return False | ||
763 | 74 | admin = getUtility(ILaunchpadCelebrities).admin | ||
764 | 75 | registry_experts = getUtility(ILaunchpadCelebrities).registry_experts | ||
765 | 76 | if ( | ||
766 | 77 | not user.is_probationary | ||
767 | 78 | or user.inTeam(self.productseries.product.owner) | ||
768 | 79 | or user.canAccess(self.sourcepackage, "setBranch") | ||
769 | 80 | or user.inTeam(registry_experts) | ||
770 | 81 | or user.inTeam(admin) | ||
771 | 82 | ): | ||
772 | 83 | return True | ||
773 | 84 | return False | ||
774 | 85 | |||
775 | 86 | def destroySelf(self): | 66 | def destroySelf(self): |
776 | 87 | if not self.userCanDelete(): | ||
777 | 88 | raise Unauthorized( | ||
778 | 89 | "Only the person who created the packaging and package " | ||
779 | 90 | "maintainers can delete it." | ||
780 | 91 | ) | ||
781 | 92 | notify(ObjectDeletedEvent(self)) | 67 | notify(ObjectDeletedEvent(self)) |
782 | 93 | super().destroySelf() | 68 | super().destroySelf() |
783 | 94 | 69 | ||
784 | @@ -97,16 +72,15 @@ class Packaging(SQLBase): | |||
785 | 97 | class PackagingUtil: | 72 | class PackagingUtil: |
786 | 98 | """Utilities for Packaging.""" | 73 | """Utilities for Packaging.""" |
787 | 99 | 74 | ||
788 | 100 | @classmethod | ||
789 | 101 | def createPackaging( | 75 | def createPackaging( |
791 | 102 | cls, productseries, sourcepackagename, distroseries, packaging, owner | 76 | self, productseries, sourcepackagename, distroseries, packaging, owner |
792 | 103 | ): | 77 | ): |
793 | 104 | """See `IPackaging`. | 78 | """See `IPackaging`. |
794 | 105 | 79 | ||
795 | 106 | Raises an assertion error if there is already packaging for | 80 | Raises an assertion error if there is already packaging for |
796 | 107 | the sourcepackagename in the distroseries. | 81 | the sourcepackagename in the distroseries. |
797 | 108 | """ | 82 | """ |
799 | 109 | if cls.packagingEntryExists(sourcepackagename, distroseries): | 83 | if self.packagingEntryExists(sourcepackagename, distroseries): |
800 | 110 | raise AssertionError( | 84 | raise AssertionError( |
801 | 111 | "A packaging entry for %s in %s already exists." | 85 | "A packaging entry for %s in %s already exists." |
802 | 112 | % (sourcepackagename.name, distroseries.name) | 86 | % (sourcepackagename.name, distroseries.name) |
803 | @@ -132,9 +106,18 @@ class PackagingUtil: | |||
804 | 132 | owner=owner, | 106 | owner=owner, |
805 | 133 | ) | 107 | ) |
806 | 134 | 108 | ||
807 | 109 | def get(self, productseries, sourcepackagename, distroseries): | ||
808 | 110 | criteria = { | ||
809 | 111 | "sourcepackagename": sourcepackagename, | ||
810 | 112 | "distroseries": distroseries, | ||
811 | 113 | } | ||
812 | 114 | if productseries is not None: | ||
813 | 115 | criteria["productseries"] = productseries | ||
814 | 116 | return Packaging.selectOneBy(**criteria) | ||
815 | 117 | |||
816 | 135 | def deletePackaging(self, productseries, sourcepackagename, distroseries): | 118 | def deletePackaging(self, productseries, sourcepackagename, distroseries): |
817 | 136 | """See `IPackaging`.""" | 119 | """See `IPackaging`.""" |
819 | 137 | packaging = Packaging.selectOneBy( | 120 | packaging = getUtility(IPackagingUtil).get( |
820 | 138 | productseries=productseries, | 121 | productseries=productseries, |
821 | 139 | sourcepackagename=sourcepackagename, | 122 | sourcepackagename=sourcepackagename, |
822 | 140 | distroseries=distroseries, | 123 | distroseries=distroseries, |
823 | @@ -152,17 +135,13 @@ class PackagingUtil: | |||
824 | 152 | ) | 135 | ) |
825 | 153 | packaging.destroySelf() | 136 | packaging.destroySelf() |
826 | 154 | 137 | ||
827 | 155 | @staticmethod | ||
828 | 156 | def packagingEntryExists( | 138 | def packagingEntryExists( |
830 | 157 | sourcepackagename, distroseries, productseries=None | 139 | self, sourcepackagename, distroseries, productseries=None |
831 | 158 | ): | 140 | ): |
832 | 159 | """See `IPackaging`.""" | 141 | """See `IPackaging`.""" |
835 | 160 | criteria = dict( | 142 | packaging = self.get( |
836 | 161 | sourcepackagename=sourcepackagename, distroseries=distroseries | 143 | productseries=productseries, |
837 | 144 | sourcepackagename=sourcepackagename, | ||
838 | 145 | distroseries=distroseries, | ||
839 | 162 | ) | 146 | ) |
846 | 163 | if productseries is not None: | 147 | return packaging is not None |
841 | 164 | criteria["productseries"] = productseries | ||
842 | 165 | result = Packaging.selectOneBy(**criteria) | ||
843 | 166 | if result is None: | ||
844 | 167 | return False | ||
845 | 168 | return True | ||
847 | diff --git a/lib/lp/registry/model/productseries.py b/lib/lp/registry/model/productseries.py | |||
848 | index 9574999..10d30ea 100644 | |||
849 | --- a/lib/lp/registry/model/productseries.py | |||
850 | +++ b/lib/lp/registry/model/productseries.py | |||
851 | @@ -17,6 +17,7 @@ from storm.locals import And, Desc | |||
852 | 17 | from storm.store import Store | 17 | from storm.store import Store |
853 | 18 | from zope.component import getUtility | 18 | from zope.component import getUtility |
854 | 19 | from zope.interface import implementer | 19 | from zope.interface import implementer |
855 | 20 | from zope.security.proxy import removeSecurityProxy | ||
856 | 20 | 21 | ||
857 | 21 | from lp.app.enums import service_uses_launchpad | 22 | from lp.app.enums import service_uses_launchpad |
858 | 22 | from lp.app.errors import NotFoundError | 23 | from lp.app.errors import NotFoundError |
859 | @@ -35,7 +36,7 @@ from lp.bugs.model.structuralsubscription import ( | |||
860 | 35 | StructuralSubscriptionTargetMixin, | 36 | StructuralSubscriptionTargetMixin, |
861 | 36 | ) | 37 | ) |
862 | 37 | from lp.registry.errors import ProprietaryPillar | 38 | from lp.registry.errors import ProprietaryPillar |
864 | 38 | from lp.registry.interfaces.packaging import PackagingType | 39 | from lp.registry.interfaces.packaging import IPackagingUtil, PackagingType |
865 | 39 | from lp.registry.interfaces.person import validate_person | 40 | from lp.registry.interfaces.person import validate_person |
866 | 40 | from lp.registry.interfaces.productrelease import IProductReleaseSet | 41 | from lp.registry.interfaces.productrelease import IProductReleaseSet |
867 | 41 | from lp.registry.interfaces.productseries import ( | 42 | from lp.registry.interfaces.productseries import ( |
868 | @@ -45,7 +46,6 @@ from lp.registry.interfaces.productseries import ( | |||
869 | 45 | ) | 46 | ) |
870 | 46 | from lp.registry.interfaces.series import SeriesStatus | 47 | from lp.registry.interfaces.series import SeriesStatus |
871 | 47 | from lp.registry.model.milestone import HasMilestonesMixin, Milestone | 48 | from lp.registry.model.milestone import HasMilestonesMixin, Milestone |
872 | 48 | from lp.registry.model.packaging import PackagingUtil | ||
873 | 49 | from lp.registry.model.productrelease import ProductRelease | 49 | from lp.registry.model.productrelease import ProductRelease |
874 | 50 | from lp.registry.model.series import SeriesMixin | 50 | from lp.registry.model.series import SeriesMixin |
875 | 51 | from lp.services.database.constants import UTC_NOW | 51 | from lp.services.database.constants import UTC_NOW |
876 | @@ -445,14 +445,14 @@ class ProductSeries( | |||
877 | 445 | 445 | ||
878 | 446 | # ok, we didn't find a packaging record that matches, let's go ahead | 446 | # ok, we didn't find a packaging record that matches, let's go ahead |
879 | 447 | # and create one | 447 | # and create one |
881 | 448 | pkg = PackagingUtil.createPackaging( | 448 | pkg = getUtility(IPackagingUtil).createPackaging( |
882 | 449 | distroseries=distroseries, | 449 | distroseries=distroseries, |
883 | 450 | sourcepackagename=sourcepackagename, | 450 | sourcepackagename=sourcepackagename, |
884 | 451 | productseries=self, | 451 | productseries=self, |
885 | 452 | packaging=PackagingType.PRIME, | 452 | packaging=PackagingType.PRIME, |
886 | 453 | owner=owner, | 453 | owner=owner, |
887 | 454 | ) | 454 | ) |
889 | 455 | pkg.sync() # convert UTC_NOW to actual datetime | 455 | removeSecurityProxy(pkg).sync() # convert UTC_NOW to actual datetime |
890 | 456 | return pkg | 456 | return pkg |
891 | 457 | 457 | ||
892 | 458 | def getPackagingInDistribution(self, distribution): | 458 | def getPackagingInDistribution(self, distribution): |
893 | diff --git a/lib/lp/registry/model/sourcepackage.py b/lib/lp/registry/model/sourcepackage.py | |||
894 | index 1bfacba..8a0280b 100644 | |||
895 | --- a/lib/lp/registry/model/sourcepackage.py | |||
896 | +++ b/lib/lp/registry/model/sourcepackage.py | |||
897 | @@ -33,14 +33,14 @@ from lp.code.model.seriessourcepackagebranch import ( | |||
898 | 33 | SeriesSourcePackageBranchSet, | 33 | SeriesSourcePackageBranchSet, |
899 | 34 | ) | 34 | ) |
900 | 35 | from lp.registry.interfaces.distribution import NoPartnerArchive | 35 | from lp.registry.interfaces.distribution import NoPartnerArchive |
902 | 36 | from lp.registry.interfaces.packaging import PackagingType | 36 | from lp.registry.interfaces.packaging import IPackagingUtil, PackagingType |
903 | 37 | from lp.registry.interfaces.pocket import PackagePublishingPocket | 37 | from lp.registry.interfaces.pocket import PackagePublishingPocket |
904 | 38 | from lp.registry.interfaces.sourcepackage import ( | 38 | from lp.registry.interfaces.sourcepackage import ( |
905 | 39 | ISourcePackage, | 39 | ISourcePackage, |
906 | 40 | ISourcePackageFactory, | 40 | ISourcePackageFactory, |
907 | 41 | ) | 41 | ) |
908 | 42 | from lp.registry.model.hasdrivers import HasDriversMixin | 42 | from lp.registry.model.hasdrivers import HasDriversMixin |
910 | 43 | from lp.registry.model.packaging import Packaging, PackagingUtil | 43 | from lp.registry.model.packaging import Packaging |
911 | 44 | from lp.registry.model.suitesourcepackage import SuiteSourcePackage | 44 | from lp.registry.model.suitesourcepackage import SuiteSourcePackage |
912 | 45 | from lp.services.database.decoratedresultset import DecoratedResultSet | 45 | from lp.services.database.decoratedresultset import DecoratedResultSet |
913 | 46 | from lp.services.database.interfaces import IStore | 46 | from lp.services.database.interfaces import IStore |
914 | @@ -562,7 +562,7 @@ class SourcePackage( | |||
915 | 562 | # Delete the current packaging and create a new one so | 562 | # Delete the current packaging and create a new one so |
916 | 563 | # that the translation sharing jobs are started. | 563 | # that the translation sharing jobs are started. |
917 | 564 | self.direct_packaging.destroySelf() | 564 | self.direct_packaging.destroySelf() |
919 | 565 | PackagingUtil.createPackaging( | 565 | getUtility(IPackagingUtil).createPackaging( |
920 | 566 | distroseries=self.distroseries, | 566 | distroseries=self.distroseries, |
921 | 567 | sourcepackagename=self.sourcepackagename, | 567 | sourcepackagename=self.sourcepackagename, |
922 | 568 | productseries=productseries, | 568 | productseries=productseries, |
923 | @@ -572,11 +572,6 @@ class SourcePackage( | |||
924 | 572 | # and make sure this change is immediately available | 572 | # and make sure this change is immediately available |
925 | 573 | flush_database_updates() | 573 | flush_database_updates() |
926 | 574 | 574 | ||
927 | 575 | def setPackagingReturnSharingDetailPermissions(self, productseries, owner): | ||
928 | 576 | """See `ISourcePackage`.""" | ||
929 | 577 | self.setPackaging(productseries, owner) | ||
930 | 578 | return self.getSharingDetailPermissions() | ||
931 | 579 | |||
932 | 580 | def getSharingDetailPermissions(self): | 575 | def getSharingDetailPermissions(self): |
933 | 581 | user = getUtility(ILaunchBag).user | 576 | user = getUtility(ILaunchBag).user |
934 | 582 | productseries = self.productseries | 577 | productseries = self.productseries |
935 | @@ -595,8 +590,8 @@ class SourcePackage( | |||
936 | 595 | else: | 590 | else: |
937 | 596 | permissions.update( | 591 | permissions.update( |
938 | 597 | { | 592 | { |
941 | 598 | "user_can_change_product_series": ( | 593 | "user_can_change_product_series": user.canAccess( |
942 | 599 | self.direct_packaging.userCanDelete() | 594 | self, "setPackaging" |
943 | 600 | ), | 595 | ), |
944 | 601 | "user_can_change_branch": user.canWrite( | 596 | "user_can_change_branch": user.canWrite( |
945 | 602 | productseries, "branch" | 597 | productseries, "branch" |
946 | diff --git a/lib/lp/registry/security.py b/lib/lp/registry/security.py | |||
947 | index 2e14ae0..ad48865 100644 | |||
948 | --- a/lib/lp/registry/security.py | |||
949 | +++ b/lib/lp/registry/security.py | |||
950 | @@ -219,8 +219,14 @@ class EditProduct(EditByOwnersOrAdmins): | |||
951 | 219 | ) | 219 | ) |
952 | 220 | 220 | ||
953 | 221 | 221 | ||
955 | 222 | class EditPackaging(EditByOwnersOrAdmins): | 222 | class EditPackaging(AuthorizationBase): |
956 | 223 | usedfor = IPackaging | 223 | usedfor = IPackaging |
957 | 224 | permission = "launchpad.Edit" | ||
958 | 225 | |||
959 | 226 | def checkAuthenticated(self, user): | ||
960 | 227 | return self.forwardCheckAuthenticated( | ||
961 | 228 | user, self.obj.productseries | ||
962 | 229 | ) or self.forwardCheckAuthenticated(user, self.obj.sourcepackage) | ||
963 | 224 | 230 | ||
964 | 225 | 231 | ||
965 | 226 | class DownloadFullSourcePackageTranslations(OnlyRosettaExpertsAndAdmins): | 232 | class DownloadFullSourcePackageTranslations(OnlyRosettaExpertsAndAdmins): |
966 | diff --git a/lib/lp/registry/stories/packaging/xx-distributionsourcepackage-packaging-concurrent-deletion.rst b/lib/lp/registry/stories/packaging/xx-distributionsourcepackage-packaging-concurrent-deletion.rst | |||
967 | index f87fa18..d53abcf 100644 | |||
968 | --- a/lib/lp/registry/stories/packaging/xx-distributionsourcepackage-packaging-concurrent-deletion.rst | |||
969 | +++ b/lib/lp/registry/stories/packaging/xx-distributionsourcepackage-packaging-concurrent-deletion.rst | |||
970 | @@ -5,12 +5,12 @@ When two browsers are used to concurrently delete the same packaging | |||
971 | 5 | association, only one of them can succeed. The other one does not oops | 5 | association, only one of them can succeed. The other one does not oops |
972 | 6 | and displays a meaningful error message. | 6 | and displays a meaningful error message. |
973 | 7 | 7 | ||
975 | 8 | The No Privilege User may open the same page in two browser tabs. | 8 | The package maintainer may open the same page in two browser tabs. |
976 | 9 | 9 | ||
978 | 10 | >>> first_browser = setupBrowser(auth="Basic test@canonical.com:test") | 10 | >>> first_browser = setupBrowser(auth="Basic admin@canonical.com:test") |
979 | 11 | >>> first_browser.open("http://launchpad.test/ubuntu/+source/alsa-utils") | 11 | >>> first_browser.open("http://launchpad.test/ubuntu/+source/alsa-utils") |
980 | 12 | 12 | ||
982 | 13 | >>> second_browser = setupBrowser(auth="Basic test@canonical.com:test") | 13 | >>> second_browser = setupBrowser(auth="Basic admin@canonical.com:test") |
983 | 14 | >>> second_browser.open("http://launchpad.test/ubuntu/+source/alsa-utils") | 14 | >>> second_browser.open("http://launchpad.test/ubuntu/+source/alsa-utils") |
984 | 15 | 15 | ||
985 | 16 | Then the user click the "Delete Link" button in the first tab. The | 16 | Then the user click the "Delete Link" button in the first tab. The |
986 | diff --git a/lib/lp/registry/stories/packaging/xx-distributionsourcepackage-packaging.rst b/lib/lp/registry/stories/packaging/xx-distributionsourcepackage-packaging.rst | |||
987 | index c616717..3f66179 100644 | |||
988 | --- a/lib/lp/registry/stories/packaging/xx-distributionsourcepackage-packaging.rst | |||
989 | +++ b/lib/lp/registry/stories/packaging/xx-distributionsourcepackage-packaging.rst | |||
990 | @@ -18,7 +18,7 @@ source package in each series of this distribution. | |||
991 | 18 | >>> anon_browser.open("http://launchpad.test/ubuntu/+source/alsa-utils") | 18 | >>> anon_browser.open("http://launchpad.test/ubuntu/+source/alsa-utils") |
992 | 19 | >>> content = anon_browser.contents | 19 | >>> content = anon_browser.contents |
993 | 20 | >>> print(extract_text(find_tag_by_id(content, "packages_list"))) | 20 | >>> print(extract_text(find_tag_by_id(content, "packages_list"))) |
995 | 21 | The Hoary Hedgehog Release (active development) Set upstream link | 21 | The Hoary Hedgehog Release (active development) |
996 | 22 | 1.0.9a-4ubuntu1 release (main) 2005-09-15 | 22 | 1.0.9a-4ubuntu1 release (main) 2005-09-15 |
997 | 23 | The Warty Warthog Release (current stable release) alsa-utils trunk series | 23 | The Warty Warthog Release (current stable release) alsa-utils trunk series |
998 | 24 | 1.0.9a-4 release (main) 2005-09-16 | 24 | 1.0.9a-4 release (main) 2005-09-16 |
999 | @@ -28,12 +28,12 @@ source package in each series of this distribution. | |||
1000 | 28 | Delete Link Button | 28 | Delete Link Button |
1001 | 29 | ------------------ | 29 | ------------------ |
1002 | 30 | 30 | ||
1004 | 31 | A button is displayed to authenticated users to delete existing | 31 | A button is displayed to project owners to delete existing |
1005 | 32 | packaging links. | 32 | packaging links. |
1006 | 33 | 33 | ||
1010 | 34 | >>> user_browser = setupBrowser(auth="Basic test@canonical.com:test") | 34 | >>> owner_browser = setupBrowser(auth="Basic mark@example.com:test") |
1011 | 35 | >>> user_browser.open("http://launchpad.test/ubuntu/+source/alsa-utils") | 35 | >>> owner_browser.open("http://launchpad.test/ubuntu/+source/alsa-utils") |
1012 | 36 | >>> link = user_browser.getLink( | 36 | >>> link = owner_browser.getLink( |
1013 | 37 | ... url="/ubuntu/warty/+source/alsa-utils/+remove-packaging" | 37 | ... url="/ubuntu/warty/+source/alsa-utils/+remove-packaging" |
1014 | 38 | ... ) | 38 | ... ) |
1015 | 39 | >>> print(link) | 39 | >>> print(link) |
1016 | @@ -49,12 +49,12 @@ This button is not displayed to anonymous users. | |||
1017 | 49 | 49 | ||
1018 | 50 | Clicking this button deletes the corresponding packaging association. | 50 | Clicking this button deletes the corresponding packaging association. |
1019 | 51 | 51 | ||
1021 | 52 | >>> link = user_browser.getLink( | 52 | >>> link = owner_browser.getLink( |
1022 | 53 | ... url="/ubuntu/warty/+source/alsa-utils/+remove-packaging" | 53 | ... url="/ubuntu/warty/+source/alsa-utils/+remove-packaging" |
1023 | 54 | ... ) | 54 | ... ) |
1024 | 55 | >>> link.click() | 55 | >>> link.click() |
1027 | 56 | >>> user_browser.getControl("Unlink").click() | 56 | >>> owner_browser.getControl("Unlink").click() |
1028 | 57 | >>> content = user_browser.contents | 57 | >>> content = owner_browser.contents |
1029 | 58 | >>> for tag in find_tags_by_class(content, "error"): | 58 | >>> for tag in find_tags_by_class(content, "error"): |
1030 | 59 | ... print(extract_text(tag)) | 59 | ... print(extract_text(tag)) |
1031 | 60 | ... | 60 | ... |
1032 | diff --git a/lib/lp/registry/stories/packaging/xx-sourcepackage-packaging.rst b/lib/lp/registry/stories/packaging/xx-sourcepackage-packaging.rst | |||
1033 | index 9729757..71e645e 100644 | |||
1034 | --- a/lib/lp/registry/stories/packaging/xx-sourcepackage-packaging.rst | |||
1035 | +++ b/lib/lp/registry/stories/packaging/xx-sourcepackage-packaging.rst | |||
1036 | @@ -10,25 +10,20 @@ Create test data. | |||
1037 | 10 | >>> test_publisher.updatePackageCache(test_data["distroseries"]) | 10 | >>> test_publisher.updatePackageCache(test_data["distroseries"]) |
1038 | 11 | >>> logout() | 11 | >>> logout() |
1039 | 12 | 12 | ||
1042 | 13 | No Privileges Person visit the distroseries upstream links page for Hoary | 13 | A person with permissions to edit packaging visits the distroseries upstream |
1043 | 14 | and sees that pmount is not linked. | 14 | links page for Hoary and sees that pmount is not linked. |
1044 | 15 | 15 | ||
1049 | 16 | >>> user_browser.open( | 16 | >>> browser = setupBrowser(auth="Basic limi@plone.org:test") |
1050 | 17 | ... "http://launchpad.test/ubuntu/hoary/+needs-packaging" | 17 | >>> browser.open("http://launchpad.test/ubuntu/hoary/+needs-packaging") |
1051 | 18 | ... ) | 18 | >>> print(extract_text(find_tag_by_id(browser.contents, "packages"))) |
1048 | 19 | >>> print(extract_text(find_tag_by_id(user_browser.contents, "packages"))) | ||
1052 | 20 | Source Package Bugs Translations | 19 | Source Package Bugs Translations |
1053 | 21 | pmount No bugs 64 strings ... | 20 | pmount No bugs 64 strings ... |
1054 | 22 | 21 | ||
1055 | 23 | They look at the pmount source package page in Hoary and read that the | 22 | They look at the pmount source package page in Hoary and read that the |
1056 | 24 | upstream project is not set. | 23 | upstream project is not set. |
1057 | 25 | 24 | ||
1064 | 26 | >>> user_browser.getLink("pmount").click() | 25 | >>> browser.getLink("pmount").click() |
1065 | 27 | >>> print( | 26 | >>> print(extract_text(find_tag_by_id(browser.contents, "no-upstreams"))) |
1060 | 28 | ... extract_text( | ||
1061 | 29 | ... find_tag_by_id(user_browser.contents, "no-upstreams") | ||
1062 | 30 | ... ) | ||
1063 | 31 | ... ) | ||
1066 | 32 | Launchpad... | 27 | Launchpad... |
1067 | 33 | There are no projects registered in Launchpad that are a potential | 28 | There are no projects registered in Launchpad that are a potential |
1068 | 34 | match for this source package. Can you help us find one? | 29 | match for this source package. Can you help us find one? |
1069 | @@ -36,46 +31,36 @@ upstream project is not set. | |||
1070 | 36 | Choose another upstream project | 31 | Choose another upstream project |
1071 | 37 | Register the upstream project | 32 | Register the upstream project |
1072 | 38 | 33 | ||
1074 | 39 | No Privileges Person knows that the pmount package comes from the thunderbird | 34 | The person knows that the pmount package comes from the thunderbird |
1075 | 40 | project. They set the upstream packaging link and see that it is set. | 35 | project. They set the upstream packaging link and see that it is set. |
1076 | 41 | 36 | ||
1088 | 42 | >>> user_browser.getControl( | 37 | >>> browser.getControl("Choose another upstream project").selected = True |
1089 | 43 | ... "Choose another upstream project" | 38 | >>> browser.getControl("Link to Upstream Project").click() |
1090 | 44 | ... ).selected = True | 39 | >>> browser.getControl(name="field.product").value = "thunderbird" |
1091 | 45 | >>> user_browser.getControl("Link to Upstream Project").click() | 40 | >>> browser.getControl("Continue").click() |
1092 | 46 | >>> user_browser.getControl(name="field.product").value = "thunderbird" | 41 | >>> browser.getControl(name="field.productseries").value = ["trunk"] |
1093 | 47 | >>> user_browser.getControl("Continue").click() | 42 | >>> browser.getControl("Change").click() |
1094 | 48 | >>> user_browser.getControl(name="field.productseries").value = ["trunk"] | 43 | >>> print(extract_text(find_tag_by_id(browser.contents, "upstreams"))) |
1084 | 49 | >>> user_browser.getControl("Change").click() | ||
1085 | 50 | >>> print( | ||
1086 | 51 | ... extract_text(find_tag_by_id(user_browser.contents, "upstreams")) | ||
1087 | 52 | ... ) | ||
1095 | 53 | The Mozilla Project...Mozilla Thunderbird...trunk... | 44 | The Mozilla Project...Mozilla Thunderbird...trunk... |
1096 | 54 | 45 | ||
1097 | 55 | They see the "Show upstream links" link and take a look at the project's | 46 | They see the "Show upstream links" link and take a look at the project's |
1098 | 56 | packaging in distributions. | 47 | packaging in distributions. |
1099 | 57 | 48 | ||
1101 | 58 | >>> user_browser.getLink("Show upstream links").click() | 49 | >>> browser.getLink("Show upstream links").click() |
1102 | 59 | >>> print( | 50 | >>> print( |
1103 | 60 | ... extract_text( | 51 | ... extract_text( |
1105 | 61 | ... find_tag_by_id(user_browser.contents, "distribution-series") | 52 | ... find_tag_by_id(browser.contents, "distribution-series") |
1106 | 62 | ... ) | 53 | ... ) |
1107 | 63 | ... ) | 54 | ... ) |
1108 | 64 | Distribution series Source package Version Project series | 55 | Distribution series Source package Version Project series |
1109 | 65 | Hoary (5.04) pmount 0.1-2 Mozilla Thunderbird trunk... | 56 | Hoary (5.04) pmount 0.1-2 Mozilla Thunderbird trunk... |
1110 | 66 | 57 | ||
1112 | 67 | No Privileges Person returns to the pmount source package page, sees the | 58 | The person returns to the pmount source package page, sees the |
1113 | 68 | link to all versions and follows it to the distro source package page. | 59 | link to all versions and follows it to the distro source package page. |
1114 | 69 | 60 | ||
1124 | 70 | >>> user_browser.getLink("pmount").click() | 61 | >>> browser.getLink("pmount").click() |
1125 | 71 | >>> user_browser.getLink( | 62 | >>> browser.getLink("All versions of pmount source in Ubuntu").click() |
1126 | 72 | ... "All versions of pmount source in Ubuntu" | 63 | >>> print(extract_text(find_tag_by_id(browser.contents, "packages_list"))) |
1118 | 73 | ... ).click() | ||
1119 | 74 | >>> print( | ||
1120 | 75 | ... extract_text( | ||
1121 | 76 | ... find_tag_by_id(user_browser.contents, "packages_list") | ||
1122 | 77 | ... ) | ||
1123 | 78 | ... ) | ||
1127 | 79 | The Hoary Hedgehog Release (active development) ... | 64 | The Hoary Hedgehog Release (active development) ... |
1128 | 80 | 0.1-2 release (main) 2005-08-24 | 65 | 0.1-2 release (main) 2005-08-24 |
1129 | 81 | 66 | ||
1130 | @@ -83,73 +68,61 @@ link to all versions and follows it to the distro source package page. | |||
1131 | 83 | Register a project from a source package | 68 | Register a project from a source package |
1132 | 84 | ---------------------------------------- | 69 | ---------------------------------------- |
1133 | 85 | 70 | ||
1135 | 86 | No Privileges Person can register a project for a package, and Launchpad | 71 | The person can register a project for a package, and Launchpad |
1136 | 87 | will use the data from the source package to prefill the first | 72 | will use the data from the source package to prefill the first |
1137 | 88 | step of the multistep form. | 73 | step of the multistep form. |
1138 | 89 | 74 | ||
1147 | 90 | >>> user_browser.open( | 75 | >>> browser = setupBrowser(auth="Basic owner@youbuntu.com:test") |
1148 | 91 | ... "http://launchpad.test/youbuntu/busy/+source/bonkers" | 76 | |
1149 | 92 | ... ) | 77 | >>> browser.open("http://launchpad.test/youbuntu/busy/+source/bonkers") |
1150 | 93 | >>> user_browser.getControl( | 78 | >>> browser.getControl("Register the upstream project").selected = True |
1151 | 94 | ... "Register the upstream project" | 79 | >>> browser.getControl("Link to Upstream Project").click() |
1152 | 95 | ... ).selected = True | 80 | >>> print(browser.getControl(name="field.name").value) |
1145 | 96 | >>> user_browser.getControl("Link to Upstream Project").click() | ||
1146 | 97 | >>> print(user_browser.getControl(name="field.name").value) | ||
1153 | 98 | bonkers | 81 | bonkers |
1155 | 99 | >>> print(user_browser.getControl(name="field.display_name").value) | 82 | >>> print(browser.getControl(name="field.display_name").value) |
1156 | 100 | Bonkers | 83 | Bonkers |
1158 | 101 | >>> print(user_browser.getControl(name="field.summary").value) | 84 | >>> print(browser.getControl(name="field.summary").value) |
1159 | 102 | summary for flubber-bin | 85 | summary for flubber-bin |
1160 | 103 | summary for flubber-lib | 86 | summary for flubber-lib |
1164 | 104 | >>> print( | 87 | >>> print(extract_text(find_tag_by_id(browser.contents, "step-title"))) |
1162 | 105 | ... extract_text(find_tag_by_id(user_browser.contents, "step-title")) | ||
1163 | 106 | ... ) | ||
1165 | 107 | Step 2 (of 2): Check for duplicate projects | 88 | Step 2 (of 2): Check for duplicate projects |
1166 | 108 | 89 | ||
1168 | 109 | When No Privileges Person selects "Choose another upstream project" and | 90 | When the person selects "Choose another upstream project" and |
1169 | 110 | then finds out that the project doesn't exist, they use the | 91 | then finds out that the project doesn't exist, they use the |
1170 | 111 | "Link to Upstream Project" button to register the project. | 92 | "Link to Upstream Project" button to register the project. |
1171 | 112 | 93 | ||
1180 | 113 | >>> user_browser.open( | 94 | >>> browser.open("http://launchpad.test/youbuntu/busy/+source/bonkers/") |
1181 | 114 | ... "http://launchpad.test/youbuntu/busy/+source/bonkers/" | 95 | >>> browser.getControl("Choose another upstream project").selected = True |
1182 | 115 | ... ) | 96 | >>> browser.getControl("Link to Upstream Project").click() |
1183 | 116 | >>> user_browser.getControl( | 97 | >>> print(browser.url) |
1176 | 117 | ... "Choose another upstream project" | ||
1177 | 118 | ... ).selected = True | ||
1178 | 119 | >>> user_browser.getControl("Link to Upstream Project").click() | ||
1179 | 120 | >>> print(user_browser.url) | ||
1184 | 121 | http://launchpad.test/youbuntu/busy/+source/bonkers/+edit-packaging | 98 | http://launchpad.test/youbuntu/busy/+source/bonkers/+edit-packaging |
1185 | 122 | 99 | ||
1188 | 123 | >>> user_browser.getLink("Register the upstream project").click() | 100 | >>> browser.getLink("Register the upstream project").click() |
1189 | 124 | >>> print(user_browser.getControl(name="field.name").value) | 101 | >>> print(browser.getControl(name="field.name").value) |
1190 | 125 | bonkers | 102 | bonkers |
1192 | 126 | >>> print(user_browser.getControl(name="field.display_name").value) | 103 | >>> print(browser.getControl(name="field.display_name").value) |
1193 | 127 | Bonkers | 104 | Bonkers |
1195 | 128 | >>> print(user_browser.getControl(name="field.summary").value) | 105 | >>> print(browser.getControl(name="field.summary").value) |
1196 | 129 | summary for flubber-bin | 106 | summary for flubber-bin |
1197 | 130 | summary for flubber-lib | 107 | summary for flubber-lib |
1201 | 131 | >>> print( | 108 | >>> print(extract_text(find_tag_by_id(browser.contents, "step-title"))) |
1199 | 132 | ... extract_text(find_tag_by_id(user_browser.contents, "step-title")) | ||
1200 | 133 | ... ) | ||
1202 | 134 | Step 2 (of 2): Check for duplicate projects | 109 | Step 2 (of 2): Check for duplicate projects |
1203 | 135 | 110 | ||
1205 | 136 | After No Privileges Person selects the licences, the user is redirected back | 111 | After the person selects the licences, the user is redirected back |
1206 | 137 | to the source package page and an informational message will be displayed. | 112 | to the source package page and an informational message will be displayed. |
1207 | 138 | 113 | ||
1210 | 139 | >>> user_browser.getControl(name="field.licenses").value = ["BSD"] | 114 | >>> browser.getControl(name="field.licenses").value = ["BSD"] |
1211 | 140 | >>> user_browser.getControl( | 115 | >>> browser.getControl( |
1212 | 141 | ... "Complete registration and link to bonkers package" | 116 | ... "Complete registration and link to bonkers package" |
1213 | 142 | ... ).click() | 117 | ... ).click() |
1215 | 143 | >>> print(user_browser.url) | 118 | >>> print(browser.url) |
1216 | 144 | http://launchpad.test/youbuntu/busy/+source/bonkers | 119 | http://launchpad.test/youbuntu/busy/+source/bonkers |
1217 | 145 | >>> for tag in find_tags_by_class( | 120 | >>> for tag in find_tags_by_class( |
1219 | 146 | ... user_browser.contents, "informational message" | 121 | ... browser.contents, "informational message" |
1220 | 147 | ... ): | 122 | ... ): |
1221 | 148 | ... print(extract_text(tag)) | 123 | ... print(extract_text(tag)) |
1222 | 149 | Linked Bonkers project to bonkers source package. | 124 | Linked Bonkers project to bonkers source package. |
1226 | 150 | >>> print( | 125 | >>> print(extract_text(find_tag_by_id(browser.contents, "upstreams"))) |
1224 | 151 | ... extract_text(find_tag_by_id(user_browser.contents, "upstreams")) | ||
1225 | 152 | ... ) | ||
1227 | 153 | Bonkers ā trunk | 126 | Bonkers ā trunk |
1228 | 154 | Change upstream link | 127 | Change upstream link |
1229 | 155 | Remove upstream link... | 128 | Remove upstream link... |
1230 | diff --git a/lib/lp/registry/stories/product/xx-product-package-pages.rst b/lib/lp/registry/stories/product/xx-product-package-pages.rst | |||
1231 | index 8d6f05a..8c160aa 100644 | |||
1232 | --- a/lib/lp/registry/stories/product/xx-product-package-pages.rst | |||
1233 | +++ b/lib/lp/registry/stories/product/xx-product-package-pages.rst | |||
1234 | @@ -42,11 +42,13 @@ Evolution. | |||
1235 | 42 | >>> evo_owner.getLink(url="/ubuntu/hoary/+source/evolution") is not None | 42 | >>> evo_owner.getLink(url="/ubuntu/hoary/+source/evolution") is not None |
1236 | 43 | True | 43 | True |
1237 | 44 | 44 | ||
1239 | 45 | Any logged in users can still see the links to create a packaging link. | 45 | Arbitrary users can't see the links to create a packaging link. |
1240 | 46 | 46 | ||
1241 | 47 | >>> user_browser.open("http://launchpad.test/evolution/+packages") | 47 | >>> user_browser.open("http://launchpad.test/evolution/+packages") |
1244 | 48 | >>> print(user_browser.getLink(url="/evolution/trunk/+ubuntupkg").url) | 48 | >>> user_browser.getLink(url="/evolution/trunk/+ubuntupkg") |
1245 | 49 | http://launchpad.test/evolution/trunk/+ubuntupkg | 49 | Traceback (most recent call last): |
1246 | 50 | ... | ||
1247 | 51 | zope.testbrowser.browser.LinkNotFoundError | ||
1248 | 50 | 52 | ||
1249 | 51 | >>> anon_browser.open("http://launchpad.test/evolution/+packages") | 53 | >>> anon_browser.open("http://launchpad.test/evolution/+packages") |
1250 | 52 | >>> anon_browser.getLink(url="/evolution/trunk/+ubuntupkg") | 54 | >>> anon_browser.getLink(url="/evolution/trunk/+ubuntupkg") |
1251 | diff --git a/lib/lp/registry/tests/test_distroseries.py b/lib/lp/registry/tests/test_distroseries.py | |||
1252 | index d3f86df..ab3c6bf 100644 | |||
1253 | --- a/lib/lp/registry/tests/test_distroseries.py | |||
1254 | +++ b/lib/lp/registry/tests/test_distroseries.py | |||
1255 | @@ -580,7 +580,7 @@ class TestDistroSeriesPackaging(TestCaseWithFactory): | |||
1256 | 580 | 580 | ||
1257 | 581 | def linkPackage(self, name): | 581 | def linkPackage(self, name): |
1258 | 582 | product_series = self.factory.makeProductSeries() | 582 | product_series = self.factory.makeProductSeries() |
1260 | 583 | product_series.setPackaging( | 583 | removeSecurityProxy(product_series).setPackaging( |
1261 | 584 | self.series, self.packages[name].sourcepackagename, self.user | 584 | self.series, self.packages[name].sourcepackagename, self.user |
1262 | 585 | ) | 585 | ) |
1263 | 586 | return product_series | 586 | return product_series |
1264 | diff --git a/lib/lp/registry/tests/test_packaging.py b/lib/lp/registry/tests/test_packaging.py | |||
1265 | index 628bfb6..f4b4db6 100644 | |||
1266 | --- a/lib/lp/registry/tests/test_packaging.py | |||
1267 | +++ b/lib/lp/registry/tests/test_packaging.py | |||
1268 | @@ -19,6 +19,8 @@ from lp.registry.model.packaging import Packaging | |||
1269 | 19 | from lp.testing import ( | 19 | from lp.testing import ( |
1270 | 20 | EventRecorder, | 20 | EventRecorder, |
1271 | 21 | TestCaseWithFactory, | 21 | TestCaseWithFactory, |
1272 | 22 | admin_logged_in, | ||
1273 | 23 | anonymous_logged_in, | ||
1274 | 22 | login, | 24 | login, |
1275 | 23 | person_logged_in, | 25 | person_logged_in, |
1276 | 24 | ) | 26 | ) |
1277 | @@ -61,7 +63,7 @@ class TestPackaging(TestCaseWithFactory): | |||
1278 | 61 | packaging.distroseries, | 63 | packaging.distroseries, |
1279 | 62 | ) | 64 | ) |
1280 | 63 | 65 | ||
1282 | 64 | def test_destroySelf__not_allowed_for_probationary_user(self): | 66 | def test_destroySelf__not_allowed_for_random_user(self): |
1283 | 65 | """Arbitrary users cannot delete a packaging.""" | 67 | """Arbitrary users cannot delete a packaging.""" |
1284 | 66 | packaging = self.factory.makePackagingLink() | 68 | packaging = self.factory.makePackagingLink() |
1285 | 67 | packaging_util = getUtility(IPackagingUtil) | 69 | packaging_util = getUtility(IPackagingUtil) |
1286 | @@ -74,26 +76,6 @@ class TestPackaging(TestCaseWithFactory): | |||
1287 | 74 | packaging.distroseries, | 76 | packaging.distroseries, |
1288 | 75 | ) | 77 | ) |
1289 | 76 | 78 | ||
1290 | 77 | def test_destroySelf__allowed_for_non_probationary_user(self): | ||
1291 | 78 | """An experienced user can delete a packaging.""" | ||
1292 | 79 | packaging = self.factory.makePackagingLink() | ||
1293 | 80 | sourcepackagename = packaging.sourcepackagename | ||
1294 | 81 | distroseries = packaging.distroseries | ||
1295 | 82 | productseries = packaging.productseries | ||
1296 | 83 | packaging_util = getUtility(IPackagingUtil) | ||
1297 | 84 | user = self.factory.makePerson(karma=200) | ||
1298 | 85 | with person_logged_in(user): | ||
1299 | 86 | packaging_util.deletePackaging( | ||
1300 | 87 | packaging.productseries, | ||
1301 | 88 | packaging.sourcepackagename, | ||
1302 | 89 | packaging.distroseries, | ||
1303 | 90 | ) | ||
1304 | 91 | self.assertFalse( | ||
1305 | 92 | packaging_util.packagingEntryExists( | ||
1306 | 93 | sourcepackagename, distroseries, productseries | ||
1307 | 94 | ) | ||
1308 | 95 | ) | ||
1309 | 96 | |||
1310 | 97 | def test_destroySelf__allowed_for_uploader(self): | 79 | def test_destroySelf__allowed_for_uploader(self): |
1311 | 98 | """A person with upload rights for the sourcepackage can | 80 | """A person with upload rights for the sourcepackage can |
1312 | 99 | delete a packaging link. | 81 | delete a packaging link. |
1313 | @@ -335,8 +317,7 @@ class TestDeletePackaging(TestCaseWithFactory): | |||
1314 | 335 | """Deleting a Packaging creates a notification.""" | 317 | """Deleting a Packaging creates a notification.""" |
1315 | 336 | packaging_util = getUtility(IPackagingUtil) | 318 | packaging_util = getUtility(IPackagingUtil) |
1316 | 337 | packaging = self.factory.makePackagingLink() | 319 | packaging = self.factory.makePackagingLink() |
1319 | 338 | user = self.factory.makePerson(karma=200) | 320 | with person_logged_in(packaging.productseries.product.owner): |
1318 | 339 | with person_logged_in(user): | ||
1320 | 340 | with EventRecorder() as recorder: | 321 | with EventRecorder() as recorder: |
1321 | 341 | packaging_util.deletePackaging( | 322 | packaging_util.deletePackaging( |
1322 | 342 | packaging.productseries, | 323 | packaging.productseries, |
1323 | @@ -346,3 +327,61 @@ class TestDeletePackaging(TestCaseWithFactory): | |||
1324 | 346 | (event,) = recorder.events | 327 | (event,) = recorder.events |
1325 | 347 | self.assertIsInstance(event, ObjectDeletedEvent) | 328 | self.assertIsInstance(event, ObjectDeletedEvent) |
1326 | 348 | self.assertIs(removeSecurityProxy(packaging), event.object) | 329 | self.assertIs(removeSecurityProxy(packaging), event.object) |
1327 | 330 | |||
1328 | 331 | |||
1329 | 332 | class TestPackagingSecurity(TestCaseWithFactory): | ||
1330 | 333 | |||
1331 | 334 | layer = DatabaseFunctionalLayer | ||
1332 | 335 | |||
1333 | 336 | def setUp(self, *args, **kwargs): | ||
1334 | 337 | super().setUp(*args, **kwargs) | ||
1335 | 338 | self.packaging = self.factory.makePackagingLink() | ||
1336 | 339 | |||
1337 | 340 | def viewPackaging(self): | ||
1338 | 341 | _ = self.packaging.packaging | ||
1339 | 342 | |||
1340 | 343 | def editPackaging(self): | ||
1341 | 344 | self.packaging.packaging = PackagingType.PRIME | ||
1342 | 345 | |||
1343 | 346 | def test_anyone_can_view(self): | ||
1344 | 347 | with anonymous_logged_in(): | ||
1345 | 348 | try: | ||
1346 | 349 | self.viewPackaging() | ||
1347 | 350 | except Unauthorized: | ||
1348 | 351 | self.fail("Unauthorized exception raised") | ||
1349 | 352 | |||
1350 | 353 | def test_anonymous_person_cannot_edit(self): | ||
1351 | 354 | with anonymous_logged_in(): | ||
1352 | 355 | self.assertRaises(Unauthorized, self.editPackaging) | ||
1353 | 356 | |||
1354 | 357 | def test_random_person_cannot_edit(self): | ||
1355 | 358 | with person_logged_in(self.factory.makePerson()): | ||
1356 | 359 | self.assertRaises(Unauthorized, self.editPackaging) | ||
1357 | 360 | |||
1358 | 361 | def test_admin_can_edit(self): | ||
1359 | 362 | with admin_logged_in(): | ||
1360 | 363 | try: | ||
1361 | 364 | self.editPackaging() | ||
1362 | 365 | except Unauthorized: | ||
1363 | 366 | self.fail("Unauthorized exception raised") | ||
1364 | 367 | |||
1365 | 368 | def test_product_owner_can_edit(self): | ||
1366 | 369 | with person_logged_in(self.packaging.productseries.product.owner): | ||
1367 | 370 | try: | ||
1368 | 371 | self.editPackaging() | ||
1369 | 372 | except Unauthorized: | ||
1370 | 373 | self.fail("Unauthorized exception raised") | ||
1371 | 374 | |||
1372 | 375 | def test_productseries_owner_can_edit(self): | ||
1373 | 376 | with person_logged_in(self.packaging.productseries.owner): | ||
1374 | 377 | try: | ||
1375 | 378 | self.editPackaging() | ||
1376 | 379 | except Unauthorized: | ||
1377 | 380 | self.fail("Unauthorized exception raised") | ||
1378 | 381 | |||
1379 | 382 | def test_distribution_owner_can_edit(self): | ||
1380 | 383 | with person_logged_in(self.packaging.distroseries.distribution.owner): | ||
1381 | 384 | try: | ||
1382 | 385 | self.editPackaging() | ||
1383 | 386 | except Unauthorized: | ||
1384 | 387 | self.fail("Unauthorized exception raised") | ||
1385 | diff --git a/lib/lp/registry/tests/test_productseries.py b/lib/lp/registry/tests/test_productseries.py | |||
1386 | index e0ea6a0..8f6b0fb 100644 | |||
1387 | --- a/lib/lp/registry/tests/test_productseries.py | |||
1388 | +++ b/lib/lp/registry/tests/test_productseries.py | |||
1389 | @@ -665,7 +665,7 @@ class ProductSeriesSecurityAdaperTestCase(TestCaseWithFactory): | |||
1390 | 665 | "addSubscription", | 665 | "addSubscription", |
1391 | 666 | "removeBugSubscription", | 666 | "removeBugSubscription", |
1392 | 667 | }, | 667 | }, |
1394 | 668 | "launchpad.Edit": {"newMilestone"}, | 668 | "launchpad.Edit": {"newMilestone", "setPackaging"}, |
1395 | 669 | "launchpad.LimitedView": { | 669 | "launchpad.LimitedView": { |
1396 | 670 | "bugtargetdisplayname", | 670 | "bugtargetdisplayname", |
1397 | 671 | "bugtarget_parent", | 671 | "bugtarget_parent", |
1398 | @@ -746,7 +746,6 @@ class ProductSeriesSecurityAdaperTestCase(TestCaseWithFactory): | |||
1399 | 746 | "releases", | 746 | "releases", |
1400 | 747 | "releaseverstyle", | 747 | "releaseverstyle", |
1401 | 748 | "searchTasks", | 748 | "searchTasks", |
1402 | 749 | "setPackaging", | ||
1403 | 750 | "sourcepackages", | 749 | "sourcepackages", |
1404 | 751 | "specifications", | 750 | "specifications", |
1405 | 752 | "status", | 751 | "status", |
1406 | diff --git a/lib/lp/registry/tests/test_sourcepackage.py b/lib/lp/registry/tests/test_sourcepackage.py | |||
1407 | index 63e976a..b839a18 100644 | |||
1408 | --- a/lib/lp/registry/tests/test_sourcepackage.py | |||
1409 | +++ b/lib/lp/registry/tests/test_sourcepackage.py | |||
1410 | @@ -33,6 +33,7 @@ from lp.testing import ( | |||
1411 | 33 | EventRecorder, | 33 | EventRecorder, |
1412 | 34 | TestCaseWithFactory, | 34 | TestCaseWithFactory, |
1413 | 35 | WebServiceTestCase, | 35 | WebServiceTestCase, |
1414 | 36 | admin_logged_in, | ||
1415 | 36 | person_logged_in, | 37 | person_logged_in, |
1416 | 37 | ) | 38 | ) |
1417 | 38 | from lp.testing.layers import DatabaseFunctionalLayer | 39 | from lp.testing.layers import DatabaseFunctionalLayer |
1418 | @@ -305,11 +306,10 @@ class TestSourcePackage(TestCaseWithFactory): | |||
1419 | 305 | 306 | ||
1420 | 306 | def test_deletePackaging(self): | 307 | def test_deletePackaging(self): |
1421 | 307 | """Ensure deletePackaging completely removes packaging.""" | 308 | """Ensure deletePackaging completely removes packaging.""" |
1422 | 308 | user = self.factory.makePerson(karma=200) | ||
1423 | 309 | packaging = self.factory.makePackagingLink() | 309 | packaging = self.factory.makePackagingLink() |
1424 | 310 | packaging_id = packaging.id | 310 | packaging_id = packaging.id |
1425 | 311 | store = Store.of(packaging) | 311 | store = Store.of(packaging) |
1427 | 312 | with person_logged_in(user): | 312 | with person_logged_in(packaging.sourcepackage.distribution.owner): |
1428 | 313 | packaging.sourcepackage.deletePackaging() | 313 | packaging.sourcepackage.deletePackaging() |
1429 | 314 | result = store.find(Packaging, Packaging.id == packaging_id) | 314 | result = store.find(Packaging, Packaging.id == packaging_id) |
1430 | 315 | self.assertIs(None, result.one()) | 315 | self.assertIs(None, result.one()) |
1431 | @@ -318,7 +318,7 @@ class TestSourcePackage(TestCaseWithFactory): | |||
1432 | 318 | """setPackaging() creates a Packaging link.""" | 318 | """setPackaging() creates a Packaging link.""" |
1433 | 319 | sourcepackage = self.factory.makeSourcePackage() | 319 | sourcepackage = self.factory.makeSourcePackage() |
1434 | 320 | productseries = self.factory.makeProductSeries() | 320 | productseries = self.factory.makeProductSeries() |
1436 | 321 | sourcepackage.setPackaging( | 321 | removeSecurityProxy(sourcepackage).setPackaging( |
1437 | 322 | productseries, owner=self.factory.makePerson() | 322 | productseries, owner=self.factory.makePerson() |
1438 | 323 | ) | 323 | ) |
1439 | 324 | packaging = sourcepackage.direct_packaging | 324 | packaging = sourcepackage.direct_packaging |
1440 | @@ -329,10 +329,9 @@ class TestSourcePackage(TestCaseWithFactory): | |||
1441 | 329 | sourcepackage = self.factory.makeSourcePackage() | 329 | sourcepackage = self.factory.makeSourcePackage() |
1442 | 330 | productseries = self.factory.makeProductSeries() | 330 | productseries = self.factory.makeProductSeries() |
1443 | 331 | other_series = self.factory.makeProductSeries() | 331 | other_series = self.factory.makeProductSeries() |
1444 | 332 | user = self.factory.makePerson(karma=200) | ||
1445 | 333 | registrant = self.factory.makePerson() | 332 | registrant = self.factory.makePerson() |
1446 | 334 | with EventRecorder() as recorder: | 333 | with EventRecorder() as recorder: |
1448 | 335 | with person_logged_in(user): | 334 | with person_logged_in(sourcepackage.distribution.owner): |
1449 | 336 | sourcepackage.setPackaging(productseries, owner=registrant) | 335 | sourcepackage.setPackaging(productseries, owner=registrant) |
1450 | 337 | sourcepackage.setPackaging(other_series, owner=registrant) | 336 | sourcepackage.setPackaging(other_series, owner=registrant) |
1451 | 338 | packaging = sourcepackage.direct_packaging | 337 | packaging = sourcepackage.direct_packaging |
1452 | @@ -348,61 +347,26 @@ class TestSourcePackage(TestCaseWithFactory): | |||
1453 | 348 | 347 | ||
1454 | 349 | def test_refuses_PROPRIETARY(self): | 348 | def test_refuses_PROPRIETARY(self): |
1455 | 350 | """Packaging cannot be created for PROPRIETARY productseries""" | 349 | """Packaging cannot be created for PROPRIETARY productseries""" |
1456 | 351 | owner = self.factory.makePerson() | ||
1457 | 352 | product = self.factory.makeProduct( | 350 | product = self.factory.makeProduct( |
1459 | 353 | owner=owner, information_type=InformationType.PROPRIETARY | 351 | information_type=InformationType.PROPRIETARY |
1460 | 354 | ) | 352 | ) |
1461 | 355 | series = self.factory.makeProductSeries(product=product) | 353 | series = self.factory.makeProductSeries(product=product) |
1462 | 356 | ubuntu_series = self.factory.makeUbuntuDistroSeries() | 354 | ubuntu_series = self.factory.makeUbuntuDistroSeries() |
1463 | 357 | sp = self.factory.makeSourcePackage(distroseries=ubuntu_series) | 355 | sp = self.factory.makeSourcePackage(distroseries=ubuntu_series) |
1465 | 358 | with person_logged_in(owner): | 356 | with admin_logged_in(): |
1466 | 359 | with ExpectedException( | 357 | with ExpectedException( |
1467 | 360 | CannotPackageProprietaryProduct, | 358 | CannotPackageProprietaryProduct, |
1468 | 361 | "Only Public project series can be packaged, not " | 359 | "Only Public project series can be packaged, not " |
1469 | 362 | "Proprietary.", | 360 | "Proprietary.", |
1470 | 363 | ): | 361 | ): |
1506 | 364 | sp.setPackaging(series, owner) | 362 | sp.setPackaging(series, product.owner) |
1472 | 365 | |||
1473 | 366 | def test_setPackagingReturnSharingDetailPermissions__ordinary_user(self): | ||
1474 | 367 | """An ordinary user can create a packaging link but they cannot | ||
1475 | 368 | set the series' branch or translation syncronisation settings, | ||
1476 | 369 | or the translation usage settings of the product. | ||
1477 | 370 | """ | ||
1478 | 371 | sourcepackage = self.factory.makeSourcePackage() | ||
1479 | 372 | productseries = self.factory.makeProductSeries() | ||
1480 | 373 | packaging_owner = self.factory.makePerson(karma=100) | ||
1481 | 374 | with person_logged_in(packaging_owner): | ||
1482 | 375 | permissions = ( | ||
1483 | 376 | sourcepackage.setPackagingReturnSharingDetailPermissions( | ||
1484 | 377 | productseries, packaging_owner | ||
1485 | 378 | ) | ||
1486 | 379 | ) | ||
1487 | 380 | self.assertEqual(productseries, sourcepackage.productseries) | ||
1488 | 381 | self.assertFalse(packaging_owner.canWrite(productseries, "branch")) | ||
1489 | 382 | self.assertFalse( | ||
1490 | 383 | packaging_owner.canWrite( | ||
1491 | 384 | productseries, "translations_autoimport_mode" | ||
1492 | 385 | ) | ||
1493 | 386 | ) | ||
1494 | 387 | self.assertFalse( | ||
1495 | 388 | packaging_owner.canWrite( | ||
1496 | 389 | productseries.product, "translations_usage" | ||
1497 | 390 | ) | ||
1498 | 391 | ) | ||
1499 | 392 | expected = { | ||
1500 | 393 | "user_can_change_product_series": True, | ||
1501 | 394 | "user_can_change_branch": False, | ||
1502 | 395 | "user_can_change_translation_usage": False, | ||
1503 | 396 | "user_can_change_translations_autoimport_mode": False, | ||
1504 | 397 | } | ||
1505 | 398 | self.assertEqual(expected, permissions) | ||
1507 | 399 | 363 | ||
1508 | 400 | def test_getSharingDetailPermissions__ordinary_user(self): | 364 | def test_getSharingDetailPermissions__ordinary_user(self): |
1509 | 401 | """An ordinary user cannot set the series' branch or translation | 365 | """An ordinary user cannot set the series' branch or translation |
1510 | 402 | synchronisation settings, or the translation usage settings of the | 366 | synchronisation settings, or the translation usage settings of the |
1511 | 403 | product. | 367 | product. |
1512 | 404 | """ | 368 | """ |
1514 | 405 | user = self.factory.makePerson(karma=100) | 369 | user = self.factory.makePerson() |
1515 | 406 | packaging = self.factory.makePackagingLink() | 370 | packaging = self.factory.makePackagingLink() |
1516 | 407 | sourcepackage = packaging.sourcepackage | 371 | sourcepackage = packaging.sourcepackage |
1517 | 408 | productseries = packaging.productseries | 372 | productseries = packaging.productseries |
1518 | @@ -417,7 +381,7 @@ class TestSourcePackage(TestCaseWithFactory): | |||
1519 | 417 | user.canWrite(productseries.product, "translations_usage") | 381 | user.canWrite(productseries.product, "translations_usage") |
1520 | 418 | ) | 382 | ) |
1521 | 419 | expected = { | 383 | expected = { |
1523 | 420 | "user_can_change_product_series": True, | 384 | "user_can_change_product_series": False, |
1524 | 421 | "user_can_change_branch": False, | 385 | "user_can_change_branch": False, |
1525 | 422 | "user_can_change_translation_usage": False, | 386 | "user_can_change_translation_usage": False, |
1526 | 423 | "user_can_change_translations_autoimport_mode": False, | 387 | "user_can_change_translations_autoimport_mode": False, |
1527 | @@ -429,8 +393,8 @@ class TestSourcePackage(TestCaseWithFactory): | |||
1528 | 429 | return self.factory.makeProductSeries(owner=self.factory.makePerson()) | 393 | return self.factory.makeProductSeries(owner=self.factory.makePerson()) |
1529 | 430 | 394 | ||
1530 | 431 | def test_getSharingDetailPermissions__product_owner(self): | 395 | def test_getSharingDetailPermissions__product_owner(self): |
1533 | 432 | """A product owner can create a packaging link, and they can set the | 396 | """A product owner can't change a packaging link, and they can set the |
1534 | 433 | series' branch and the translation syncronisation settings, and the | 397 | series' branch and the translation synchronisation settings, and the |
1535 | 434 | translation usage settings of the product. | 398 | translation usage settings of the product. |
1536 | 435 | """ | 399 | """ |
1537 | 436 | productseries = self.makeDistinctOwnerProductSeries() | 400 | productseries = self.makeDistinctOwnerProductSeries() |
1538 | @@ -454,7 +418,7 @@ class TestSourcePackage(TestCaseWithFactory): | |||
1539 | 454 | ) | 418 | ) |
1540 | 455 | ) | 419 | ) |
1541 | 456 | expected = { | 420 | expected = { |
1543 | 457 | "user_can_change_product_series": True, | 421 | "user_can_change_product_series": False, |
1544 | 458 | "user_can_change_branch": True, | 422 | "user_can_change_branch": True, |
1545 | 459 | "user_can_change_translation_usage": True, | 423 | "user_can_change_translation_usage": True, |
1546 | 460 | "user_can_change_translations_autoimport_mode": True, | 424 | "user_can_change_translations_autoimport_mode": True, |
1547 | @@ -464,34 +428,26 @@ class TestSourcePackage(TestCaseWithFactory): | |||
1548 | 464 | def test_getSharingDetailPermissions_change_product(self): | 428 | def test_getSharingDetailPermissions_change_product(self): |
1549 | 465 | """Test user_can_change_product_series. | 429 | """Test user_can_change_product_series. |
1550 | 466 | 430 | ||
1553 | 467 | Until a Packaging is created, anyone can change product series. | 431 | Random people cannot create package link or change product series. |
1552 | 468 | Afterward, random people cannot change product series. | ||
1554 | 469 | """ | 432 | """ |
1555 | 470 | sourcepackage = self.factory.makeSourcePackage() | 433 | sourcepackage = self.factory.makeSourcePackage() |
1558 | 471 | person1 = self.factory.makePerson(karma=100) | 434 | person = self.factory.makePerson() |
1557 | 472 | person2 = self.factory.makePerson() | ||
1559 | 473 | 435 | ||
1560 | 474 | def can_change_product_series(): | 436 | def can_change_product_series(): |
1561 | 475 | return sourcepackage.getSharingDetailPermissions()[ | 437 | return sourcepackage.getSharingDetailPermissions()[ |
1562 | 476 | "user_can_change_product_series" | 438 | "user_can_change_product_series" |
1563 | 477 | ] | 439 | ] |
1564 | 478 | 440 | ||
1575 | 479 | with person_logged_in(person1): | 441 | with person_logged_in(person): |
1576 | 480 | self.assertTrue(can_change_product_series()) | 442 | self.assertFalse(can_change_product_series()) |
1577 | 481 | with person_logged_in(person2): | 443 | self.factory.makePackagingLink(sourcepackage=sourcepackage) |
1578 | 482 | self.assertTrue(can_change_product_series()) | 444 | with person_logged_in(person): |
1569 | 483 | self.factory.makePackagingLink( | ||
1570 | 484 | sourcepackage=sourcepackage, owner=person1 | ||
1571 | 485 | ) | ||
1572 | 486 | with person_logged_in(person1): | ||
1573 | 487 | self.assertTrue(can_change_product_series()) | ||
1574 | 488 | with person_logged_in(person2): | ||
1579 | 489 | self.assertFalse(can_change_product_series()) | 445 | self.assertFalse(can_change_product_series()) |
1580 | 490 | 446 | ||
1581 | 491 | def test_getSharingDetailPermissions_no_product_series(self): | 447 | def test_getSharingDetailPermissions_no_product_series(self): |
1582 | 492 | sourcepackage = self.factory.makeSourcePackage() | 448 | sourcepackage = self.factory.makeSourcePackage() |
1583 | 493 | expected = { | 449 | expected = { |
1585 | 494 | "user_can_change_product_series": True, | 450 | "user_can_change_product_series": False, |
1586 | 495 | "user_can_change_branch": False, | 451 | "user_can_change_branch": False, |
1587 | 496 | "user_can_change_translation_usage": False, | 452 | "user_can_change_translation_usage": False, |
1588 | 497 | "user_can_change_translations_autoimport_mode": False, | 453 | "user_can_change_translations_autoimport_mode": False, |
1589 | @@ -558,8 +514,12 @@ class TestSourcePackageWebService(WebServiceTestCase): | |||
1590 | 558 | self.assertIs(None, sourcepackage.direct_packaging) | 514 | self.assertIs(None, sourcepackage.direct_packaging) |
1591 | 559 | productseries = self.factory.makeProductSeries() | 515 | productseries = self.factory.makeProductSeries() |
1592 | 560 | transaction.commit() | 516 | transaction.commit() |
1595 | 561 | ws_sourcepackage = self.wsObject(sourcepackage) | 517 | ws_sourcepackage = self.wsObject( |
1596 | 562 | ws_productseries = self.wsObject(productseries) | 518 | sourcepackage, user=sourcepackage.distribution.owner |
1597 | 519 | ) | ||
1598 | 520 | ws_productseries = self.wsObject( | ||
1599 | 521 | productseries, user=sourcepackage.distribution.owner | ||
1600 | 522 | ) | ||
1601 | 563 | ws_sourcepackage.setPackaging(productseries=ws_productseries) | 523 | ws_sourcepackage.setPackaging(productseries=ws_productseries) |
1602 | 564 | transaction.commit() | 524 | transaction.commit() |
1603 | 565 | self.assertEqual( | 525 | self.assertEqual( |
1604 | @@ -568,11 +528,12 @@ class TestSourcePackageWebService(WebServiceTestCase): | |||
1605 | 568 | 528 | ||
1606 | 569 | def test_deletePackaging(self): | 529 | def test_deletePackaging(self): |
1607 | 570 | """Deleting a packaging should work.""" | 530 | """Deleting a packaging should work.""" |
1608 | 571 | user = self.factory.makePerson(karma=200) | ||
1609 | 572 | packaging = self.factory.makePackagingLink() | 531 | packaging = self.factory.makePackagingLink() |
1610 | 573 | sourcepackage = packaging.sourcepackage | 532 | sourcepackage = packaging.sourcepackage |
1611 | 574 | transaction.commit() | 533 | transaction.commit() |
1613 | 575 | self.wsObject(sourcepackage, user=user).deletePackaging() | 534 | self.wsObject( |
1614 | 535 | sourcepackage, user=sourcepackage.distribution.owner | ||
1615 | 536 | ).deletePackaging() | ||
1616 | 576 | transaction.commit() | 537 | transaction.commit() |
1617 | 577 | self.assertIs(None, sourcepackage.direct_packaging) | 538 | self.assertIs(None, sourcepackage.direct_packaging) |
1618 | 578 | 539 | ||
1619 | @@ -580,7 +541,9 @@ class TestSourcePackageWebService(WebServiceTestCase): | |||
1620 | 580 | """Deleting when there's no packaging should be a no-op.""" | 541 | """Deleting when there's no packaging should be a no-op.""" |
1621 | 581 | sourcepackage = self.factory.makeSourcePackage() | 542 | sourcepackage = self.factory.makeSourcePackage() |
1622 | 582 | transaction.commit() | 543 | transaction.commit() |
1624 | 583 | self.wsObject(sourcepackage).deletePackaging() | 544 | self.wsObject( |
1625 | 545 | sourcepackage, user=sourcepackage.distribution.owner | ||
1626 | 546 | ).deletePackaging() | ||
1627 | 584 | transaction.commit() | 547 | transaction.commit() |
1628 | 585 | self.assertIs(None, sourcepackage.direct_packaging) | 548 | self.assertIs(None, sourcepackage.direct_packaging) |
1629 | 586 | 549 | ||
1630 | @@ -737,7 +700,9 @@ class TestSourcePackageViews(TestCaseWithFactory): | |||
1631 | 737 | def test_editpackaging_obsolete_series_in_vocabulary(self): | 700 | def test_editpackaging_obsolete_series_in_vocabulary(self): |
1632 | 738 | # The sourcepackage's current product series is included in | 701 | # The sourcepackage's current product series is included in |
1633 | 739 | # the vocabulary even if it is obsolete. | 702 | # the vocabulary even if it is obsolete. |
1635 | 740 | self.package.setPackaging(self.obsolete_productseries, self.owner) | 703 | removeSecurityProxy(self.package).setPackaging( |
1636 | 704 | self.obsolete_productseries, self.owner | ||
1637 | 705 | ) | ||
1638 | 741 | form = { | 706 | form = { |
1639 | 742 | "field.product": "bonkers", | 707 | "field.product": "bonkers", |
1640 | 743 | "field.actions.continue": "Continue", | 708 | "field.actions.continue": "Continue", |
1641 | diff --git a/lib/lp/soyuz/tests/test_publishing.py b/lib/lp/soyuz/tests/test_publishing.py | |||
1642 | index cd0828d..ff6e308 100644 | |||
1643 | --- a/lib/lp/soyuz/tests/test_publishing.py | |||
1644 | +++ b/lib/lp/soyuz/tests/test_publishing.py | |||
1645 | @@ -707,7 +707,9 @@ class SoyuzTestPublisher: | |||
1646 | 707 | """ | 707 | """ |
1647 | 708 | if source_pub is None: | 708 | if source_pub is None: |
1648 | 709 | distribution = self.factory.makeDistribution( | 709 | distribution = self.factory.makeDistribution( |
1650 | 710 | name="youbuntu", displayname="Youbuntu" | 710 | name="youbuntu", |
1651 | 711 | displayname="Youbuntu", | ||
1652 | 712 | owner=self.factory.makePerson(email="owner@youbuntu.com"), | ||
1653 | 711 | ) | 713 | ) |
1654 | 712 | distroseries = self.factory.makeDistroSeries( | 714 | distroseries = self.factory.makeDistroSeries( |
1655 | 713 | name="busy", distribution=distribution | 715 | name="busy", distribution=distribution |
1656 | diff --git a/lib/lp/testing/factory.py b/lib/lp/testing/factory.py | |||
1657 | index 3fd560c..9965749 100644 | |||
1658 | --- a/lib/lp/testing/factory.py | |||
1659 | +++ b/lib/lp/testing/factory.py | |||
1660 | @@ -208,6 +208,7 @@ from lp.registry.interfaces.ssh import ISSHKeySet | |||
1661 | 208 | from lp.registry.model.commercialsubscription import CommercialSubscription | 208 | from lp.registry.model.commercialsubscription import CommercialSubscription |
1662 | 209 | from lp.registry.model.karma import KarmaTotalCache | 209 | from lp.registry.model.karma import KarmaTotalCache |
1663 | 210 | from lp.registry.model.milestone import Milestone | 210 | from lp.registry.model.milestone import Milestone |
1664 | 211 | from lp.registry.model.packaging import Packaging | ||
1665 | 211 | from lp.registry.model.suitesourcepackage import SuiteSourcePackage | 212 | from lp.registry.model.suitesourcepackage import SuiteSourcePackage |
1666 | 212 | from lp.services.auth.interfaces import IAccessTokenSet | 213 | from lp.services.auth.interfaces import IAccessTokenSet |
1667 | 213 | from lp.services.auth.utils import create_access_token_secret | 214 | from lp.services.auth.utils import create_access_token_secret |
1668 | @@ -1346,7 +1347,7 @@ class LaunchpadObjectFactory(ObjectFactory): | |||
1669 | 1346 | owner=None, | 1347 | owner=None, |
1670 | 1347 | sourcepackage=None, | 1348 | sourcepackage=None, |
1671 | 1348 | in_ubuntu=False, | 1349 | in_ubuntu=False, |
1673 | 1349 | ): | 1350 | ) -> Packaging: |
1674 | 1350 | assert sourcepackage is None or ( | 1351 | assert sourcepackage is None or ( |
1675 | 1351 | distroseries is None and sourcepackagename is None | 1352 | distroseries is None and sourcepackagename is None |
1676 | 1352 | ), ( | 1353 | ), ( |