Merge ~twom/launchpad:oci-policy-set-the-official-with-the-official-permissions into launchpad:master
- Git
- lp:~twom/launchpad
- oci-policy-set-the-official-with-the-official-permissions
- Merge into master
Proposed by
Tom Wardill
Status: | Merged |
---|---|
Approved by: | Tom Wardill |
Approved revision: | a9792063cf939f4c8eabf14e0ee1f7e3ce53fa77 |
Merge reported by: | Otto Co-Pilot |
Merged at revision: | not available |
Proposed branch: | ~twom/launchpad:oci-policy-set-the-official-with-the-official-permissions |
Merge into: | launchpad:master |
Diff against target: |
634 lines (+272/-104) 9 files modified
lib/lp/oci/browser/ocirecipe.py (+63/-1) lib/lp/oci/browser/tests/test_ocirecipe.py (+178/-0) lib/lp/oci/templates/ocirecipe-index.pt (+7/-0) lib/lp/oci/templates/ocirecipe-new.pt (+3/-0) lib/lp/oci/tests/test_ocirecipe.py (+11/-10) lib/lp/registry/browser/ociproject.py (+0/-11) lib/lp/registry/browser/tests/test_ociproject.py (+2/-68) lib/lp/registry/interfaces/ociproject.py (+4/-5) lib/lp/registry/model/ociproject.py (+4/-9) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ioana Lasc (community) | Approve | ||
Thiago F. Pappacena (community) | Approve | ||
Review via email: mp+396584@code.launchpad.net |
Commit message
Set official status for an OCIRecipe via the UI
Description of the change
To post a comment you must log in.
- a53392c... by Tom Wardill
-
Tidy up old remnants
Revision history for this message
Tom Wardill (twom) : | # |
Revision history for this message
Thiago F. Pappacena (pappacena) : | # |
review:
Approve
- 3cb0b3f... by Tom Wardill
-
Remove official recipe options from OCI Project
Revision history for this message
Thiago F. Pappacena (pappacena) wrote : | # |
LGTM. Added just a small comment on a test.
Now that we don't enforce a single official recipe per OCIProject, it seems a bit strange having OCIProject managing an attribute of OCIRecipe. But for me it's ok to keep it like this for now, and refactoring in the future.
- a979206... by Tom Wardill
-
Check the other status hasn't changed
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/lib/lp/oci/browser/ocirecipe.py b/lib/lp/oci/browser/ocirecipe.py |
2 | index a2b8119..7cf1f94 100644 |
3 | --- a/lib/lp/oci/browser/ocirecipe.py |
4 | +++ b/lib/lp/oci/browser/ocirecipe.py |
5 | @@ -742,6 +742,18 @@ class OCIRecipeFormMixin: |
6 | build_args[k] = v |
7 | data['build_args'] = build_args |
8 | |
9 | + def userIsRecipeAdmin(self): |
10 | + if check_permission("launchpad.Admin", self.context): |
11 | + return True |
12 | + person = getattr(self.request.principal, 'person', None) |
13 | + if not person: |
14 | + return False |
15 | + # Edit context = OCIRecipe, New context = OCIProject |
16 | + project = getattr(self.context, "oci_project", self.context) |
17 | + if project.pillar.canAdministerOCIProjects(person): |
18 | + return True |
19 | + return False |
20 | + |
21 | |
22 | class OCIRecipeAddView(LaunchpadFormView, EnableProcessorsMixin, |
23 | OCIRecipeFormMixin): |
24 | @@ -775,6 +787,16 @@ class OCIRecipeAddView(LaunchpadFormView, EnableProcessorsMixin, |
25 | "The architectures that this OCI recipe builds for. Some " |
26 | "architectures are restricted and may only be enabled or " |
27 | "disabled by administrators.") |
28 | + self.form_fields += FormFields(Bool( |
29 | + __name__="official_recipe", |
30 | + title="Official recipe", |
31 | + description=( |
32 | + "Mark this recipe as official for this OCI Project. " |
33 | + "Allows use of distribution registry credentials " |
34 | + "and the default git repository routing. " |
35 | + "May only be enabled by the owner of the OCI Project."), |
36 | + default=False, |
37 | + required=False, readonly=False)) |
38 | |
39 | def setUpGitRefWidget(self): |
40 | """Setup GitRef widget indicating the user to use the default |
41 | @@ -798,6 +820,11 @@ class OCIRecipeAddView(LaunchpadFormView, EnableProcessorsMixin, |
42 | super(OCIRecipeAddView, self).setUpWidgets() |
43 | self.widgets["processors"].widget_class = "processors" |
44 | self.setUpGitRefWidget() |
45 | + # disable the official recipe button if the user doesn't have |
46 | + # permissions to change it |
47 | + widget = self.widgets['official_recipe'] |
48 | + if not self.userIsRecipeAdmin(): |
49 | + widget.extra = "disabled='disabled'" |
50 | |
51 | @property |
52 | def cancel_url(self): |
53 | @@ -829,6 +856,12 @@ class OCIRecipeAddView(LaunchpadFormView, EnableProcessorsMixin, |
54 | "this name." % ( |
55 | owner.display_name, self.context.display_name)) |
56 | self.validateBuildArgs(data) |
57 | + official = data.get("official_recipe", None) |
58 | + if official and not self.userIsRecipeAdmin(): |
59 | + self.setFieldError( |
60 | + "official_recipe", |
61 | + "You do not have permission to set the official status " |
62 | + "of this recipe.") |
63 | |
64 | @action("Create OCI recipe", name="create") |
65 | def create_action(self, action, data): |
66 | @@ -837,7 +870,8 @@ class OCIRecipeAddView(LaunchpadFormView, EnableProcessorsMixin, |
67 | oci_project=self.context, git_ref=data["git_ref"], |
68 | build_file=data["build_file"], description=data["description"], |
69 | build_daily=data["build_daily"], build_args=data["build_args"], |
70 | - build_path=data["build_path"], processors=data["processors"]) |
71 | + build_path=data["build_path"], processors=data["processors"], |
72 | + official=data.get('official_recipe', False)) |
73 | self.next_url = canonical_url(recipe) |
74 | |
75 | |
76 | @@ -858,6 +892,11 @@ class BaseOCIRecipeEditView(LaunchpadEditFormView): |
77 | self.context.setProcessors( |
78 | new_processors, check_permissions=True, user=self.user) |
79 | del data["processors"] |
80 | + official = data.pop('official_recipe', None) |
81 | + if official is not None and self.userIsRecipeAdmin(): |
82 | + self.context.oci_project.setOfficialRecipeStatus( |
83 | + self.context, official) |
84 | + |
85 | self.updateContextFromData(data) |
86 | self.next_url = canonical_url(self.context) |
87 | |
88 | @@ -931,6 +970,11 @@ class OCIRecipeEditView(BaseOCIRecipeEditView, EnableProcessorsMixin, |
89 | """See `LaunchpadFormView`.""" |
90 | super(OCIRecipeEditView, self).setUpWidgets() |
91 | self.setUpGitRefWidget() |
92 | + # disable the official recipe button if the user doesn't have |
93 | + # permissions to change it |
94 | + widget = self.widgets['official_recipe'] |
95 | + if not self.userIsRecipeAdmin(): |
96 | + widget.extra = "disabled='disabled'" |
97 | |
98 | def setUpFields(self): |
99 | """See `LaunchpadFormView`.""" |
100 | @@ -941,6 +985,16 @@ class OCIRecipeEditView(BaseOCIRecipeEditView, EnableProcessorsMixin, |
101 | "The architectures that this OCI recipe builds for. Some " |
102 | "architectures are restricted and may only be enabled or " |
103 | "disabled by administrators.") |
104 | + self.form_fields += FormFields(Bool( |
105 | + __name__="official_recipe", |
106 | + title="Official recipe", |
107 | + description=( |
108 | + "Mark this recipe as official for this OCI Project. " |
109 | + "Allows use of distribution registry credentials " |
110 | + "and the default git repository routing. " |
111 | + "May only be enabled by the owner of the OCI Project."), |
112 | + default=False, |
113 | + required=False, readonly=False)) |
114 | |
115 | def validate(self, data): |
116 | """See `LaunchpadFormView`.""" |
117 | @@ -976,6 +1030,14 @@ class OCIRecipeEditView(BaseOCIRecipeEditView, EnableProcessorsMixin, |
118 | # enabled. Leave it untouched. |
119 | data["processors"].append(processor) |
120 | self.validateBuildArgs(data) |
121 | + official = data.get('official_recipe') |
122 | + official_change = self.context.official != official |
123 | + is_admin = self.userIsRecipeAdmin() |
124 | + if official is not None and official_change and not is_admin: |
125 | + self.setFieldError( |
126 | + "official_recipe", |
127 | + "You do not have permission to change the official status " |
128 | + "of this recipe.") |
129 | |
130 | |
131 | class OCIRecipeDeleteView(BaseOCIRecipeEditView): |
132 | diff --git a/lib/lp/oci/browser/tests/test_ocirecipe.py b/lib/lp/oci/browser/tests/test_ocirecipe.py |
133 | index 3505fd3..35b9dd8 100644 |
134 | --- a/lib/lp/oci/browser/tests/test_ocirecipe.py |
135 | +++ b/lib/lp/oci/browser/tests/test_ocirecipe.py |
136 | @@ -223,6 +223,9 @@ class TestOCIRecipeAddView(BaseTestOCIRecipeView): |
137 | self.assertThat( |
138 | "Build schedule:\nBuilt on request\nEdit OCI recipe\n", |
139 | MatchesTagText(content, "build-schedule")) |
140 | + self.assertThat( |
141 | + "Official recipe:\nNo", |
142 | + MatchesTagText(content, "official-recipe")) |
143 | |
144 | def test_create_new_recipe_with_build_args(self): |
145 | oci_project = self.factory.makeOCIProject() |
146 | @@ -335,6 +338,114 @@ class TestOCIRecipeAddView(BaseTestOCIRecipeView): |
147 | "id": "field.git_ref.repository", |
148 | "value": default_repo_path}))) |
149 | |
150 | + def test_official_is_disabled(self): |
151 | + oci_project = self.factory.makeOCIProject() |
152 | + browser = self.getViewBrowser( |
153 | + oci_project, view_name="+new-recipe", user=self.person) |
154 | + official_control = browser.getControl("Official recipe") |
155 | + self.assertTrue(official_control.disabled) |
156 | + |
157 | + def test_official_is_enabled(self): |
158 | + distribution = self.factory.makeDistribution( |
159 | + oci_project_admin=self.person) |
160 | + oci_project = self.factory.makeOCIProject(pillar=distribution) |
161 | + browser = self.getViewBrowser( |
162 | + oci_project, view_name="+new-recipe", user=self.person) |
163 | + official_control = browser.getControl("Official recipe") |
164 | + self.assertFalse(official_control.disabled) |
165 | + |
166 | + def test_set_official(self): |
167 | + distribution = self.factory.makeDistribution( |
168 | + oci_project_admin=self.person) |
169 | + oci_project = self.factory.makeOCIProject(pillar=distribution) |
170 | + [git_ref] = self.factory.makeGitRefs() |
171 | + browser = self.getViewBrowser( |
172 | + oci_project, view_name="+new-recipe", user=self.person) |
173 | + browser.getControl(name="field.name").value = "recipe-name" |
174 | + browser.getControl("Description").value = "Recipe description" |
175 | + browser.getControl(name="field.git_ref.repository").value = ( |
176 | + git_ref.repository.identity) |
177 | + browser.getControl(name="field.git_ref.path").value = git_ref.path |
178 | + official_control = browser.getControl("Official recipe") |
179 | + official_control.selected = True |
180 | + browser.getControl("Create OCI recipe").click() |
181 | + |
182 | + content = find_main_content(browser.contents) |
183 | + self.assertThat( |
184 | + "Official recipe:\nYes", |
185 | + MatchesTagText(content, "official-recipe")) |
186 | + |
187 | + def test_set_official_multiple(self): |
188 | + distribution = self.factory.makeDistribution( |
189 | + oci_project_admin=self.person) |
190 | + |
191 | + # do it once |
192 | + oci_project = self.factory.makeOCIProject(pillar=distribution) |
193 | + [git_ref] = self.factory.makeGitRefs() |
194 | + |
195 | + # and then do it again |
196 | + oci_project2 = self.factory.makeOCIProject(pillar=distribution) |
197 | + [git_ref2] = self.factory.makeGitRefs() |
198 | + browser = self.getViewBrowser( |
199 | + oci_project, view_name="+new-recipe", user=self.person) |
200 | + browser.getControl(name="field.name").value = "recipe-name" |
201 | + browser.getControl("Description").value = "Recipe description" |
202 | + browser.getControl(name="field.git_ref.repository").value = ( |
203 | + git_ref.repository.identity) |
204 | + browser.getControl(name="field.git_ref.path").value = git_ref.path |
205 | + official_control = browser.getControl("Official recipe") |
206 | + official_control.selected = True |
207 | + browser.getControl("Create OCI recipe").click() |
208 | + |
209 | + content = find_main_content(browser.contents) |
210 | + self.assertThat( |
211 | + "Official recipe:\nYes", |
212 | + MatchesTagText(content, "official-recipe")) |
213 | + |
214 | + browser2 = self.getViewBrowser( |
215 | + oci_project2, view_name="+new-recipe", user=self.person) |
216 | + browser2.getControl(name="field.name").value = "recipe-name" |
217 | + browser2.getControl("Description").value = "Recipe description" |
218 | + browser2.getControl(name="field.git_ref.repository").value = ( |
219 | + git_ref2.repository.identity) |
220 | + browser2.getControl(name="field.git_ref.path").value = git_ref2.path |
221 | + official_control = browser2.getControl("Official recipe") |
222 | + official_control.selected = True |
223 | + browser2.getControl("Create OCI recipe").click() |
224 | + |
225 | + content = find_main_content(browser2.contents) |
226 | + self.assertThat( |
227 | + "Official recipe:\nYes", |
228 | + MatchesTagText(content, "official-recipe")) |
229 | + |
230 | + browser.reload() |
231 | + content = find_main_content(browser.contents) |
232 | + self.assertThat( |
233 | + "Official recipe:\nYes", |
234 | + MatchesTagText(content, "official-recipe")) |
235 | + |
236 | + def test_set_official_no_permissions(self): |
237 | + distro_owner = self.factory.makePerson() |
238 | + distribution = self.factory.makeDistribution( |
239 | + oci_project_admin=distro_owner) |
240 | + oci_project = self.factory.makeOCIProject(pillar=distribution) |
241 | + [git_ref] = self.factory.makeGitRefs() |
242 | + browser = self.getViewBrowser( |
243 | + oci_project, view_name="+new-recipe", user=self.person) |
244 | + browser.getControl(name="field.name").value = "recipe-name" |
245 | + browser.getControl("Description").value = "Recipe description" |
246 | + browser.getControl(name="field.git_ref.repository").value = ( |
247 | + git_ref.repository.identity) |
248 | + browser.getControl(name="field.git_ref.path").value = git_ref.path |
249 | + official_control = browser.getControl("Official recipe") |
250 | + official_control.selected = True |
251 | + browser.getControl("Create OCI recipe").click() |
252 | + |
253 | + error_message = ( |
254 | + "You do not have permission to set the official status " |
255 | + "of this recipe.") |
256 | + self.assertIn(error_message, browser.contents) |
257 | + |
258 | |
259 | class TestOCIRecipeAdminView(BaseTestOCIRecipeView): |
260 | |
261 | @@ -751,6 +862,69 @@ class TestOCIRecipeEditView(OCIConfigHelperMixin, BaseTestOCIRecipeView): |
262 | self.assertNotIn(wrong_namespace_msg, browser.contents) |
263 | self.assertIn(wrong_ref_path_msg, browser.contents) |
264 | |
265 | + def test_official_is_disabled(self): |
266 | + oci_project = self.factory.makeOCIProject() |
267 | + recipe = self.factory.makeOCIRecipe( |
268 | + registrant=self.person, owner=self.person, |
269 | + oci_project=oci_project) |
270 | + |
271 | + browser = self.getViewBrowser(recipe, user=self.person) |
272 | + browser.getLink("Edit OCI recipe").click() |
273 | + official_control = browser.getControl("Official recipe") |
274 | + self.assertTrue(official_control.disabled) |
275 | + |
276 | + def test_official_is_enabled(self): |
277 | + distribution = self.factory.makeDistribution( |
278 | + oci_project_admin=self.person) |
279 | + oci_project = self.factory.makeOCIProject(pillar=distribution) |
280 | + recipe = self.factory.makeOCIRecipe( |
281 | + registrant=self.person, owner=self.person, |
282 | + oci_project=oci_project) |
283 | + |
284 | + browser = self.getViewBrowser(recipe, user=self.person) |
285 | + browser.getLink("Edit OCI recipe").click() |
286 | + official_control = browser.getControl("Official recipe") |
287 | + self.assertFalse(official_control.disabled) |
288 | + |
289 | + def test_set_official(self): |
290 | + distribution = self.factory.makeDistribution( |
291 | + oci_project_admin=self.person) |
292 | + oci_project = self.factory.makeOCIProject(pillar=distribution) |
293 | + recipe = self.factory.makeOCIRecipe( |
294 | + registrant=self.person, owner=self.person, |
295 | + oci_project=oci_project) |
296 | + |
297 | + browser = self.getViewBrowser(recipe, user=self.person) |
298 | + browser.getLink("Edit OCI recipe").click() |
299 | + official_control = browser.getControl("Official recipe") |
300 | + official_control.selected = True |
301 | + browser.getControl("Update OCI recipe").click() |
302 | + |
303 | + content = find_main_content(browser.contents) |
304 | + self.assertThat( |
305 | + "Official recipe:\nYes", |
306 | + MatchesTagText(content, "official-recipe")) |
307 | + |
308 | + def test_set_official_no_permissions(self): |
309 | + distro_owner = self.factory.makePerson() |
310 | + distribution = self.factory.makeDistribution( |
311 | + oci_project_admin=distro_owner) |
312 | + oci_project = self.factory.makeOCIProject(pillar=distribution) |
313 | + recipe = self.factory.makeOCIRecipe( |
314 | + registrant=self.person, owner=self.person, |
315 | + oci_project=oci_project) |
316 | + |
317 | + browser = self.getViewBrowser(recipe, user=self.person) |
318 | + browser.getLink("Edit OCI recipe").click() |
319 | + official_control = browser.getControl("Official recipe") |
320 | + official_control.selected = True |
321 | + browser.getControl("Update OCI recipe").click() |
322 | + |
323 | + error_message = ( |
324 | + "You do not have permission to change the official status " |
325 | + "of this recipe.") |
326 | + self.assertIn(error_message, browser.contents) |
327 | + |
328 | |
329 | class TestOCIRecipeDeleteView(BaseTestOCIRecipeView): |
330 | |
331 | @@ -901,6 +1075,8 @@ class TestOCIRecipeView(BaseTestOCIRecipeView): |
332 | Build file path: Dockerfile |
333 | Build context directory: %s |
334 | Build schedule: Built on request |
335 | + Official recipe: |
336 | + No |
337 | Latest builds |
338 | Status When complete Architecture |
339 | Successfully built 30 minutes ago 386 |
340 | @@ -934,6 +1110,8 @@ class TestOCIRecipeView(BaseTestOCIRecipeView): |
341 | Build context directory: %s |
342 | Build schedule: Built on request |
343 | Build-time\nARG variables: VAR1=123 VAR2=XXX |
344 | + Official recipe: |
345 | + No |
346 | Latest builds |
347 | Status When complete Architecture |
348 | Successfully built 30 minutes ago 386 |
349 | diff --git a/lib/lp/oci/templates/ocirecipe-index.pt b/lib/lp/oci/templates/ocirecipe-index.pt |
350 | index 9a9aefc..a3e67bb 100644 |
351 | --- a/lib/lp/oci/templates/ocirecipe-index.pt |
352 | +++ b/lib/lp/oci/templates/ocirecipe-index.pt |
353 | @@ -81,6 +81,13 @@ |
354 | <pre tal:content="build_args" /> |
355 | </dd> |
356 | </dl> |
357 | + <dl id="official-recipe"> |
358 | + <dt>Official recipe:</dt> |
359 | + <dd> |
360 | + <span tal:condition="context/official">Yes</span> |
361 | + <span tal:condition="not: context/official">No</span> |
362 | + </dd> |
363 | + </dl> |
364 | </div> |
365 | |
366 | <h2>Latest builds</h2> |
367 | diff --git a/lib/lp/oci/templates/ocirecipe-new.pt b/lib/lp/oci/templates/ocirecipe-new.pt |
368 | index 1cae71e..00ed4e2 100644 |
369 | --- a/lib/lp/oci/templates/ocirecipe-new.pt |
370 | +++ b/lib/lp/oci/templates/ocirecipe-new.pt |
371 | @@ -41,6 +41,9 @@ |
372 | <tal:widget define="widget nocall:view/widgets/processors"> |
373 | <metal:block use-macro="context/@@launchpad_form/widget_row" /> |
374 | </tal:widget> |
375 | + <tal:widget define="widget nocall:view/widgets/official_recipe"> |
376 | + <metal:block use-macro="context/@@launchpad_form/widget_row" /> |
377 | + </tal:widget> |
378 | </table> |
379 | </metal:formbody> |
380 | </div> |
381 | diff --git a/lib/lp/oci/tests/test_ocirecipe.py b/lib/lp/oci/tests/test_ocirecipe.py |
382 | index 59bd465..1cb69af 100644 |
383 | --- a/lib/lp/oci/tests/test_ocirecipe.py |
384 | +++ b/lib/lp/oci/tests/test_ocirecipe.py |
385 | @@ -592,27 +592,27 @@ class TestOCIRecipe(OCIConfigHelperMixin, TestCaseWithFactory): |
386 | oci_project=oci_project2, registrant=owner, owner=owner) |
387 | for _ in range(2)] |
388 | |
389 | - self.assertIsNone(oci_project1.getOfficialRecipe()) |
390 | - self.assertIsNone(oci_project2.getOfficialRecipe()) |
391 | + self.assertTrue(oci_project1.getOfficialRecipes().is_empty()) |
392 | + self.assertTrue(oci_project2.getOfficialRecipes().is_empty()) |
393 | for recipe in oci_proj1_recipes + oci_proj2_recipes: |
394 | self.assertFalse(recipe.official) |
395 | |
396 | # Set official for project1 and make sure nothing else got changed. |
397 | with StormStatementRecorder() as recorder: |
398 | - oci_project1.setOfficialRecipe(oci_proj1_recipes[0]) |
399 | - self.assertEqual(2, recorder.count) |
400 | + oci_project1.setOfficialRecipeStatus(oci_proj1_recipes[0], True) |
401 | + self.assertEqual(1, recorder.count) |
402 | |
403 | - self.assertIsNone(oci_project2.getOfficialRecipe()) |
404 | + self.assertTrue(oci_project2.getOfficialRecipes().is_empty()) |
405 | self.assertEqual( |
406 | - oci_proj1_recipes[0], oci_project1.getOfficialRecipe()) |
407 | + oci_proj1_recipes[0], oci_project1.getOfficialRecipes()[0]) |
408 | self.assertTrue(oci_proj1_recipes[0].official) |
409 | for recipe in oci_proj1_recipes[1:] + oci_proj2_recipes: |
410 | self.assertFalse(recipe.official) |
411 | |
412 | # Set back no recipe as official. |
413 | with StormStatementRecorder() as recorder: |
414 | - oci_project1.setOfficialRecipe(None) |
415 | - self.assertEqual(1, recorder.count) |
416 | + oci_project1.setOfficialRecipeStatus(oci_proj1_recipes[0], False) |
417 | + self.assertEqual(0, recorder.count) |
418 | |
419 | for recipe in oci_proj1_recipes + oci_proj2_recipes: |
420 | self.assertFalse(recipe.official) |
421 | @@ -630,7 +630,8 @@ class TestOCIRecipe(OCIConfigHelperMixin, TestCaseWithFactory): |
422 | oci_project=oci_project, registrant=owner) |
423 | |
424 | self.assertRaises( |
425 | - ValueError, another_oci_project.setOfficialRecipe, recipe) |
426 | + ValueError, another_oci_project.setOfficialRecipeStatus, |
427 | + recipe, True) |
428 | |
429 | def test_permission_check_on_setOfficialRecipe(self): |
430 | distro = self.factory.makeDistribution() |
431 | @@ -642,7 +643,7 @@ class TestOCIRecipe(OCIConfigHelperMixin, TestCaseWithFactory): |
432 | another_user = self.factory.makePerson() |
433 | with person_logged_in(another_user): |
434 | self.assertRaises( |
435 | - Unauthorized, getattr, oci_project, 'setOfficialRecipe') |
436 | + Unauthorized, getattr, oci_project, 'setOfficialRecipeStatus') |
437 | |
438 | def test_oci_project_get_recipe_by_name_and_owner(self): |
439 | owner = self.factory.makePerson() |
440 | diff --git a/lib/lp/registry/browser/ociproject.py b/lib/lp/registry/browser/ociproject.py |
441 | index a10d9a2..380b94a 100644 |
442 | --- a/lib/lp/registry/browser/ociproject.py |
443 | +++ b/lib/lp/registry/browser/ociproject.py |
444 | @@ -234,7 +234,6 @@ class OCIProjectEditView(LaunchpadEditFormView): |
445 | schema = IOCIProject |
446 | field_names = [ |
447 | 'name', |
448 | - 'official_recipe', |
449 | ] |
450 | |
451 | def setUpFields(self): |
452 | @@ -247,14 +246,6 @@ class OCIProjectEditView(LaunchpadEditFormView): |
453 | pillar_field = self.form_fields.get(pillar_key).field |
454 | pillar_field.required = True |
455 | |
456 | - def extendFields(self): |
457 | - official_recipe = self.context.getOfficialRecipe() |
458 | - self.form_fields += form.Fields( |
459 | - Choice( |
460 | - __name__="official_recipe", title=u"Official recipe", |
461 | - required=False, vocabulary="OCIRecipe", |
462 | - default=official_recipe)) |
463 | - |
464 | @property |
465 | def label(self): |
466 | return 'Edit %s OCI project' % self.context.name |
467 | @@ -277,9 +268,7 @@ class OCIProjectEditView(LaunchpadEditFormView): |
468 | |
469 | @action('Update OCI project', name='update') |
470 | def update_action(self, action, data): |
471 | - official_recipe = data.pop("official_recipe") |
472 | self.updateContextFromData(data) |
473 | - self.context.setOfficialRecipe(official_recipe) |
474 | |
475 | @property |
476 | def next_url(self): |
477 | diff --git a/lib/lp/registry/browser/tests/test_ociproject.py b/lib/lp/registry/browser/tests/test_ociproject.py |
478 | index 3743465..a8b4113 100644 |
479 | --- a/lib/lp/registry/browser/tests/test_ociproject.py |
480 | +++ b/lib/lp/registry/browser/tests/test_ociproject.py |
481 | @@ -154,11 +154,9 @@ class TestOCIProjectEditView(BrowserTestCase): |
482 | |
483 | layer = DatabaseFunctionalLayer |
484 | |
485 | - def submitEditForm(self, browser, name, official_recipe=''): |
486 | + def submitEditForm(self, browser, name): |
487 | browser.getLink("Edit OCI project").click() |
488 | browser.getControl(name="field.name").value = name |
489 | - browser.getControl(name="field.official_recipe").value = ( |
490 | - official_recipe) |
491 | browser.getControl("Update OCI project").click() |
492 | |
493 | def test_edit_oci_project(self): |
494 | @@ -253,7 +251,7 @@ class TestOCIProjectEditView(BrowserTestCase): |
495 | view = create_initialized_view( |
496 | oci_project, name="+edit", principal=oci_project.pillar.owner) |
497 | view.update_action.success( |
498 | - {"name": "changed", "official_recipe": None}) |
499 | + {"name": "changed"}) |
500 | self.assertSqlAttributeEqualsDate( |
501 | oci_project, "date_last_modified", UTC_NOW) |
502 | |
503 | @@ -280,70 +278,6 @@ class TestOCIProjectEditView(BrowserTestCase): |
504 | extract_text(find_tags_by_class(browser.contents, "message")[1]), |
505 | "Invalid name 'invalid name'.") |
506 | |
507 | - def test_edit_oci_project_setting_official_recipe(self): |
508 | - self.useFixture(FeatureFixture({OCI_RECIPE_ALLOW_CREATE: 'on'})) |
509 | - |
510 | - with admin_logged_in(): |
511 | - oci_project = self.factory.makeOCIProject() |
512 | - user = oci_project.pillar.owner |
513 | - recipe1 = self.factory.makeOCIRecipe( |
514 | - registrant=user, owner=user, oci_project=oci_project) |
515 | - recipe2 = self.factory.makeOCIRecipe( |
516 | - registrant=user, owner=user, oci_project=oci_project) |
517 | - |
518 | - name_value = oci_project.name |
519 | - recipe_value = "%s/%s" % (user.name, recipe1.name) |
520 | - |
521 | - browser = self.getViewBrowser(oci_project, user=user) |
522 | - self.submitEditForm(browser, name_value, recipe_value) |
523 | - |
524 | - with admin_logged_in(): |
525 | - self.assertEqual(recipe1, oci_project.getOfficialRecipe()) |
526 | - self.assertTrue(recipe1.official) |
527 | - self.assertFalse(recipe2.official) |
528 | - |
529 | - def test_edit_oci_project_overriding_official_recipe(self): |
530 | - self.useFixture(FeatureFixture({OCI_RECIPE_ALLOW_CREATE: 'on'})) |
531 | - with admin_logged_in(): |
532 | - oci_project = self.factory.makeOCIProject() |
533 | - user = oci_project.pillar.owner |
534 | - recipe1 = self.factory.makeOCIRecipe( |
535 | - registrant=user, owner=user, oci_project=oci_project) |
536 | - recipe2 = self.factory.makeOCIRecipe( |
537 | - registrant=user, owner=user, oci_project=oci_project) |
538 | - |
539 | - # Sets recipe1 as the current official one |
540 | - oci_project.setOfficialRecipe(recipe1) |
541 | - |
542 | - # And we will try to set recipe2 as the new official. |
543 | - name_value = oci_project.name |
544 | - recipe_value = "%s/%s" % (user.name, recipe2.name) |
545 | - |
546 | - browser = self.getViewBrowser(oci_project, user=user) |
547 | - self.submitEditForm(browser, name_value, recipe_value) |
548 | - |
549 | - with admin_logged_in(): |
550 | - self.assertEqual(recipe2, oci_project.getOfficialRecipe()) |
551 | - self.assertFalse(recipe1.official) |
552 | - self.assertTrue(recipe2.official) |
553 | - |
554 | - def test_edit_oci_project_unsetting_official_recipe(self): |
555 | - self.useFixture(FeatureFixture({OCI_RECIPE_ALLOW_CREATE: 'on'})) |
556 | - with admin_logged_in(): |
557 | - oci_project = self.factory.makeOCIProject() |
558 | - user = oci_project.pillar.owner |
559 | - recipe = self.factory.makeOCIRecipe( |
560 | - registrant=user, owner=user, oci_project=oci_project) |
561 | - oci_project.setOfficialRecipe(recipe) |
562 | - name_value = oci_project.name |
563 | - |
564 | - browser = self.getViewBrowser(oci_project, user=user) |
565 | - self.submitEditForm(browser, name_value, '') |
566 | - |
567 | - with admin_logged_in(): |
568 | - self.assertEqual(None, oci_project.getOfficialRecipe()) |
569 | - self.assertFalse(recipe.official) |
570 | - |
571 | |
572 | class TestOCIProjectAddView(BrowserTestCase): |
573 | |
574 | diff --git a/lib/lp/registry/interfaces/ociproject.py b/lib/lp/registry/interfaces/ociproject.py |
575 | index cf4abef..718b9d7 100644 |
576 | --- a/lib/lp/registry/interfaces/ociproject.py |
577 | +++ b/lib/lp/registry/interfaces/ociproject.py |
578 | @@ -95,8 +95,8 @@ class IOCIProjectView(IHasGitRepositories, Interface): |
579 | def searchRecipes(query): |
580 | """Searches for recipes in this OCI project.""" |
581 | |
582 | - def getOfficialRecipe(): |
583 | - """Gets the official recipe for this OCI project.""" |
584 | + def getOfficialRecipes(): |
585 | + """Gets the official recipes for this OCI project.""" |
586 | |
587 | def getDefaultGitRepository(person): |
588 | """Returns the default git repository for the given user under the |
589 | @@ -147,9 +147,8 @@ class IOCIProjectEdit(Interface): |
590 | status=SeriesStatus.DEVELOPMENT, date_created=DEFAULT): |
591 | """Creates a new `IOCIProjectSeries`.""" |
592 | |
593 | - def setOfficialRecipe(recipe): |
594 | - """Sets the given recipe as the official one. If recipe is None, |
595 | - the current official recipe will be unset.""" |
596 | + def setOfficialRecipeStatus(recipe, status): |
597 | + """Change whether an OCI Recipe is official or not for this project.""" |
598 | |
599 | |
600 | class IOCIProjectLegitimate(Interface): |
601 | diff --git a/lib/lp/registry/model/ociproject.py b/lib/lp/registry/model/ociproject.py |
602 | index 4526724..46ea7da 100644 |
603 | --- a/lib/lp/registry/model/ociproject.py |
604 | +++ b/lib/lp/registry/model/ociproject.py |
605 | @@ -200,12 +200,12 @@ class OCIProject(BugTargetBase, StormBase): |
606 | Person.name.contains_string(query)) |
607 | return q.order_by(Person.name, OCIRecipe.name) |
608 | |
609 | - def getOfficialRecipe(self): |
610 | + def getOfficialRecipes(self): |
611 | """See `IOCIProject`.""" |
612 | from lp.oci.model.ocirecipe import OCIRecipe |
613 | - return self.getRecipes().find(OCIRecipe._official == True).one() |
614 | + return self.getRecipes().find(OCIRecipe._official == True) |
615 | |
616 | - def setOfficialRecipe(self, recipe): |
617 | + def setOfficialRecipeStatus(self, recipe, status): |
618 | """See `IOCIProject`.""" |
619 | if recipe is not None and recipe.oci_project != self: |
620 | raise ValueError( |
621 | @@ -215,12 +215,7 @@ class OCIProject(BugTargetBase, StormBase): |
622 | # attribute not declared on the Interface, and we need to set it |
623 | # regardless of security checks on OCIRecipe objects. |
624 | recipe = removeSecurityProxy(recipe) |
625 | - previous = removeSecurityProxy(self.getOfficialRecipe()) |
626 | - if previous != recipe: |
627 | - if previous is not None: |
628 | - previous._official = False |
629 | - if recipe is not None: |
630 | - recipe._official = True |
631 | + recipe._official = status |
632 | |
633 | def getDefaultGitRepository(self, person): |
634 | namespace = getUtility(IGitNamespaceSet).get(person, oci_project=self) |
LGTM