Merge ~twom/launchpad:oci-policy-set-the-official-with-the-official-permissions into launchpad: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)
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

To post a comment you must log in.
a53392c... by Tom Wardill

Tidy up old remnants

Revision history for this message
Thiago F. Pappacena (pappacena) wrote :

LGTM

review: Approve
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.

Revision history for this message
Ioana Lasc (ilasc) wrote :

LGTM

review: Approve
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
1diff --git a/lib/lp/oci/browser/ocirecipe.py b/lib/lp/oci/browser/ocirecipe.py
2index 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):
132diff --git a/lib/lp/oci/browser/tests/test_ocirecipe.py b/lib/lp/oci/browser/tests/test_ocirecipe.py
133index 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
349diff --git a/lib/lp/oci/templates/ocirecipe-index.pt b/lib/lp/oci/templates/ocirecipe-index.pt
350index 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>
367diff --git a/lib/lp/oci/templates/ocirecipe-new.pt b/lib/lp/oci/templates/ocirecipe-new.pt
368index 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>
381diff --git a/lib/lp/oci/tests/test_ocirecipe.py b/lib/lp/oci/tests/test_ocirecipe.py
382index 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()
440diff --git a/lib/lp/registry/browser/ociproject.py b/lib/lp/registry/browser/ociproject.py
441index 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):
477diff --git a/lib/lp/registry/browser/tests/test_ociproject.py b/lib/lp/registry/browser/tests/test_ociproject.py
478index 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
574diff --git a/lib/lp/registry/interfaces/ociproject.py b/lib/lp/registry/interfaces/ociproject.py
575index 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):
601diff --git a/lib/lp/registry/model/ociproject.py b/lib/lp/registry/model/ociproject.py
602index 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)

Subscribers

People subscribed via source and target branches

to status/vote changes: