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
diff --git a/lib/lp/oci/browser/ocirecipe.py b/lib/lp/oci/browser/ocirecipe.py
index a2b8119..7cf1f94 100644
--- a/lib/lp/oci/browser/ocirecipe.py
+++ b/lib/lp/oci/browser/ocirecipe.py
@@ -742,6 +742,18 @@ class OCIRecipeFormMixin:
742 build_args[k] = v742 build_args[k] = v
743 data['build_args'] = build_args743 data['build_args'] = build_args
744744
745 def userIsRecipeAdmin(self):
746 if check_permission("launchpad.Admin", self.context):
747 return True
748 person = getattr(self.request.principal, 'person', None)
749 if not person:
750 return False
751 # Edit context = OCIRecipe, New context = OCIProject
752 project = getattr(self.context, "oci_project", self.context)
753 if project.pillar.canAdministerOCIProjects(person):
754 return True
755 return False
756
745757
746class OCIRecipeAddView(LaunchpadFormView, EnableProcessorsMixin,758class OCIRecipeAddView(LaunchpadFormView, EnableProcessorsMixin,
747 OCIRecipeFormMixin):759 OCIRecipeFormMixin):
@@ -775,6 +787,16 @@ class OCIRecipeAddView(LaunchpadFormView, EnableProcessorsMixin,
775 "The architectures that this OCI recipe builds for. Some "787 "The architectures that this OCI recipe builds for. Some "
776 "architectures are restricted and may only be enabled or "788 "architectures are restricted and may only be enabled or "
777 "disabled by administrators.")789 "disabled by administrators.")
790 self.form_fields += FormFields(Bool(
791 __name__="official_recipe",
792 title="Official recipe",
793 description=(
794 "Mark this recipe as official for this OCI Project. "
795 "Allows use of distribution registry credentials "
796 "and the default git repository routing. "
797 "May only be enabled by the owner of the OCI Project."),
798 default=False,
799 required=False, readonly=False))
778800
779 def setUpGitRefWidget(self):801 def setUpGitRefWidget(self):
780 """Setup GitRef widget indicating the user to use the default802 """Setup GitRef widget indicating the user to use the default
@@ -798,6 +820,11 @@ class OCIRecipeAddView(LaunchpadFormView, EnableProcessorsMixin,
798 super(OCIRecipeAddView, self).setUpWidgets()820 super(OCIRecipeAddView, self).setUpWidgets()
799 self.widgets["processors"].widget_class = "processors"821 self.widgets["processors"].widget_class = "processors"
800 self.setUpGitRefWidget()822 self.setUpGitRefWidget()
823 # disable the official recipe button if the user doesn't have
824 # permissions to change it
825 widget = self.widgets['official_recipe']
826 if not self.userIsRecipeAdmin():
827 widget.extra = "disabled='disabled'"
801828
802 @property829 @property
803 def cancel_url(self):830 def cancel_url(self):
@@ -829,6 +856,12 @@ class OCIRecipeAddView(LaunchpadFormView, EnableProcessorsMixin,
829 "this name." % (856 "this name." % (
830 owner.display_name, self.context.display_name))857 owner.display_name, self.context.display_name))
831 self.validateBuildArgs(data)858 self.validateBuildArgs(data)
859 official = data.get("official_recipe", None)
860 if official and not self.userIsRecipeAdmin():
861 self.setFieldError(
862 "official_recipe",
863 "You do not have permission to set the official status "
864 "of this recipe.")
832865
833 @action("Create OCI recipe", name="create")866 @action("Create OCI recipe", name="create")
834 def create_action(self, action, data):867 def create_action(self, action, data):
@@ -837,7 +870,8 @@ class OCIRecipeAddView(LaunchpadFormView, EnableProcessorsMixin,
837 oci_project=self.context, git_ref=data["git_ref"],870 oci_project=self.context, git_ref=data["git_ref"],
838 build_file=data["build_file"], description=data["description"],871 build_file=data["build_file"], description=data["description"],
839 build_daily=data["build_daily"], build_args=data["build_args"],872 build_daily=data["build_daily"], build_args=data["build_args"],
840 build_path=data["build_path"], processors=data["processors"])873 build_path=data["build_path"], processors=data["processors"],
874 official=data.get('official_recipe', False))
841 self.next_url = canonical_url(recipe)875 self.next_url = canonical_url(recipe)
842876
843877
@@ -858,6 +892,11 @@ class BaseOCIRecipeEditView(LaunchpadEditFormView):
858 self.context.setProcessors(892 self.context.setProcessors(
859 new_processors, check_permissions=True, user=self.user)893 new_processors, check_permissions=True, user=self.user)
860 del data["processors"]894 del data["processors"]
895 official = data.pop('official_recipe', None)
896 if official is not None and self.userIsRecipeAdmin():
897 self.context.oci_project.setOfficialRecipeStatus(
898 self.context, official)
899
861 self.updateContextFromData(data)900 self.updateContextFromData(data)
862 self.next_url = canonical_url(self.context)901 self.next_url = canonical_url(self.context)
863902
@@ -931,6 +970,11 @@ class OCIRecipeEditView(BaseOCIRecipeEditView, EnableProcessorsMixin,
931 """See `LaunchpadFormView`."""970 """See `LaunchpadFormView`."""
932 super(OCIRecipeEditView, self).setUpWidgets()971 super(OCIRecipeEditView, self).setUpWidgets()
933 self.setUpGitRefWidget()972 self.setUpGitRefWidget()
973 # disable the official recipe button if the user doesn't have
974 # permissions to change it
975 widget = self.widgets['official_recipe']
976 if not self.userIsRecipeAdmin():
977 widget.extra = "disabled='disabled'"
934978
935 def setUpFields(self):979 def setUpFields(self):
936 """See `LaunchpadFormView`."""980 """See `LaunchpadFormView`."""
@@ -941,6 +985,16 @@ class OCIRecipeEditView(BaseOCIRecipeEditView, EnableProcessorsMixin,
941 "The architectures that this OCI recipe builds for. Some "985 "The architectures that this OCI recipe builds for. Some "
942 "architectures are restricted and may only be enabled or "986 "architectures are restricted and may only be enabled or "
943 "disabled by administrators.")987 "disabled by administrators.")
988 self.form_fields += FormFields(Bool(
989 __name__="official_recipe",
990 title="Official recipe",
991 description=(
992 "Mark this recipe as official for this OCI Project. "
993 "Allows use of distribution registry credentials "
994 "and the default git repository routing. "
995 "May only be enabled by the owner of the OCI Project."),
996 default=False,
997 required=False, readonly=False))
944998
945 def validate(self, data):999 def validate(self, data):
946 """See `LaunchpadFormView`."""1000 """See `LaunchpadFormView`."""
@@ -976,6 +1030,14 @@ class OCIRecipeEditView(BaseOCIRecipeEditView, EnableProcessorsMixin,
976 # enabled. Leave it untouched.1030 # enabled. Leave it untouched.
977 data["processors"].append(processor)1031 data["processors"].append(processor)
978 self.validateBuildArgs(data)1032 self.validateBuildArgs(data)
1033 official = data.get('official_recipe')
1034 official_change = self.context.official != official
1035 is_admin = self.userIsRecipeAdmin()
1036 if official is not None and official_change and not is_admin:
1037 self.setFieldError(
1038 "official_recipe",
1039 "You do not have permission to change the official status "
1040 "of this recipe.")
9791041
9801042
981class OCIRecipeDeleteView(BaseOCIRecipeEditView):1043class OCIRecipeDeleteView(BaseOCIRecipeEditView):
diff --git a/lib/lp/oci/browser/tests/test_ocirecipe.py b/lib/lp/oci/browser/tests/test_ocirecipe.py
index 3505fd3..35b9dd8 100644
--- a/lib/lp/oci/browser/tests/test_ocirecipe.py
+++ b/lib/lp/oci/browser/tests/test_ocirecipe.py
@@ -223,6 +223,9 @@ class TestOCIRecipeAddView(BaseTestOCIRecipeView):
223 self.assertThat(223 self.assertThat(
224 "Build schedule:\nBuilt on request\nEdit OCI recipe\n",224 "Build schedule:\nBuilt on request\nEdit OCI recipe\n",
225 MatchesTagText(content, "build-schedule"))225 MatchesTagText(content, "build-schedule"))
226 self.assertThat(
227 "Official recipe:\nNo",
228 MatchesTagText(content, "official-recipe"))
226229
227 def test_create_new_recipe_with_build_args(self):230 def test_create_new_recipe_with_build_args(self):
228 oci_project = self.factory.makeOCIProject()231 oci_project = self.factory.makeOCIProject()
@@ -335,6 +338,114 @@ class TestOCIRecipeAddView(BaseTestOCIRecipeView):
335 "id": "field.git_ref.repository",338 "id": "field.git_ref.repository",
336 "value": default_repo_path})))339 "value": default_repo_path})))
337340
341 def test_official_is_disabled(self):
342 oci_project = self.factory.makeOCIProject()
343 browser = self.getViewBrowser(
344 oci_project, view_name="+new-recipe", user=self.person)
345 official_control = browser.getControl("Official recipe")
346 self.assertTrue(official_control.disabled)
347
348 def test_official_is_enabled(self):
349 distribution = self.factory.makeDistribution(
350 oci_project_admin=self.person)
351 oci_project = self.factory.makeOCIProject(pillar=distribution)
352 browser = self.getViewBrowser(
353 oci_project, view_name="+new-recipe", user=self.person)
354 official_control = browser.getControl("Official recipe")
355 self.assertFalse(official_control.disabled)
356
357 def test_set_official(self):
358 distribution = self.factory.makeDistribution(
359 oci_project_admin=self.person)
360 oci_project = self.factory.makeOCIProject(pillar=distribution)
361 [git_ref] = self.factory.makeGitRefs()
362 browser = self.getViewBrowser(
363 oci_project, view_name="+new-recipe", user=self.person)
364 browser.getControl(name="field.name").value = "recipe-name"
365 browser.getControl("Description").value = "Recipe description"
366 browser.getControl(name="field.git_ref.repository").value = (
367 git_ref.repository.identity)
368 browser.getControl(name="field.git_ref.path").value = git_ref.path
369 official_control = browser.getControl("Official recipe")
370 official_control.selected = True
371 browser.getControl("Create OCI recipe").click()
372
373 content = find_main_content(browser.contents)
374 self.assertThat(
375 "Official recipe:\nYes",
376 MatchesTagText(content, "official-recipe"))
377
378 def test_set_official_multiple(self):
379 distribution = self.factory.makeDistribution(
380 oci_project_admin=self.person)
381
382 # do it once
383 oci_project = self.factory.makeOCIProject(pillar=distribution)
384 [git_ref] = self.factory.makeGitRefs()
385
386 # and then do it again
387 oci_project2 = self.factory.makeOCIProject(pillar=distribution)
388 [git_ref2] = self.factory.makeGitRefs()
389 browser = self.getViewBrowser(
390 oci_project, view_name="+new-recipe", user=self.person)
391 browser.getControl(name="field.name").value = "recipe-name"
392 browser.getControl("Description").value = "Recipe description"
393 browser.getControl(name="field.git_ref.repository").value = (
394 git_ref.repository.identity)
395 browser.getControl(name="field.git_ref.path").value = git_ref.path
396 official_control = browser.getControl("Official recipe")
397 official_control.selected = True
398 browser.getControl("Create OCI recipe").click()
399
400 content = find_main_content(browser.contents)
401 self.assertThat(
402 "Official recipe:\nYes",
403 MatchesTagText(content, "official-recipe"))
404
405 browser2 = self.getViewBrowser(
406 oci_project2, view_name="+new-recipe", user=self.person)
407 browser2.getControl(name="field.name").value = "recipe-name"
408 browser2.getControl("Description").value = "Recipe description"
409 browser2.getControl(name="field.git_ref.repository").value = (
410 git_ref2.repository.identity)
411 browser2.getControl(name="field.git_ref.path").value = git_ref2.path
412 official_control = browser2.getControl("Official recipe")
413 official_control.selected = True
414 browser2.getControl("Create OCI recipe").click()
415
416 content = find_main_content(browser2.contents)
417 self.assertThat(
418 "Official recipe:\nYes",
419 MatchesTagText(content, "official-recipe"))
420
421 browser.reload()
422 content = find_main_content(browser.contents)
423 self.assertThat(
424 "Official recipe:\nYes",
425 MatchesTagText(content, "official-recipe"))
426
427 def test_set_official_no_permissions(self):
428 distro_owner = self.factory.makePerson()
429 distribution = self.factory.makeDistribution(
430 oci_project_admin=distro_owner)
431 oci_project = self.factory.makeOCIProject(pillar=distribution)
432 [git_ref] = self.factory.makeGitRefs()
433 browser = self.getViewBrowser(
434 oci_project, view_name="+new-recipe", user=self.person)
435 browser.getControl(name="field.name").value = "recipe-name"
436 browser.getControl("Description").value = "Recipe description"
437 browser.getControl(name="field.git_ref.repository").value = (
438 git_ref.repository.identity)
439 browser.getControl(name="field.git_ref.path").value = git_ref.path
440 official_control = browser.getControl("Official recipe")
441 official_control.selected = True
442 browser.getControl("Create OCI recipe").click()
443
444 error_message = (
445 "You do not have permission to set the official status "
446 "of this recipe.")
447 self.assertIn(error_message, browser.contents)
448
338449
339class TestOCIRecipeAdminView(BaseTestOCIRecipeView):450class TestOCIRecipeAdminView(BaseTestOCIRecipeView):
340451
@@ -751,6 +862,69 @@ class TestOCIRecipeEditView(OCIConfigHelperMixin, BaseTestOCIRecipeView):
751 self.assertNotIn(wrong_namespace_msg, browser.contents)862 self.assertNotIn(wrong_namespace_msg, browser.contents)
752 self.assertIn(wrong_ref_path_msg, browser.contents)863 self.assertIn(wrong_ref_path_msg, browser.contents)
753864
865 def test_official_is_disabled(self):
866 oci_project = self.factory.makeOCIProject()
867 recipe = self.factory.makeOCIRecipe(
868 registrant=self.person, owner=self.person,
869 oci_project=oci_project)
870
871 browser = self.getViewBrowser(recipe, user=self.person)
872 browser.getLink("Edit OCI recipe").click()
873 official_control = browser.getControl("Official recipe")
874 self.assertTrue(official_control.disabled)
875
876 def test_official_is_enabled(self):
877 distribution = self.factory.makeDistribution(
878 oci_project_admin=self.person)
879 oci_project = self.factory.makeOCIProject(pillar=distribution)
880 recipe = self.factory.makeOCIRecipe(
881 registrant=self.person, owner=self.person,
882 oci_project=oci_project)
883
884 browser = self.getViewBrowser(recipe, user=self.person)
885 browser.getLink("Edit OCI recipe").click()
886 official_control = browser.getControl("Official recipe")
887 self.assertFalse(official_control.disabled)
888
889 def test_set_official(self):
890 distribution = self.factory.makeDistribution(
891 oci_project_admin=self.person)
892 oci_project = self.factory.makeOCIProject(pillar=distribution)
893 recipe = self.factory.makeOCIRecipe(
894 registrant=self.person, owner=self.person,
895 oci_project=oci_project)
896
897 browser = self.getViewBrowser(recipe, user=self.person)
898 browser.getLink("Edit OCI recipe").click()
899 official_control = browser.getControl("Official recipe")
900 official_control.selected = True
901 browser.getControl("Update OCI recipe").click()
902
903 content = find_main_content(browser.contents)
904 self.assertThat(
905 "Official recipe:\nYes",
906 MatchesTagText(content, "official-recipe"))
907
908 def test_set_official_no_permissions(self):
909 distro_owner = self.factory.makePerson()
910 distribution = self.factory.makeDistribution(
911 oci_project_admin=distro_owner)
912 oci_project = self.factory.makeOCIProject(pillar=distribution)
913 recipe = self.factory.makeOCIRecipe(
914 registrant=self.person, owner=self.person,
915 oci_project=oci_project)
916
917 browser = self.getViewBrowser(recipe, user=self.person)
918 browser.getLink("Edit OCI recipe").click()
919 official_control = browser.getControl("Official recipe")
920 official_control.selected = True
921 browser.getControl("Update OCI recipe").click()
922
923 error_message = (
924 "You do not have permission to change the official status "
925 "of this recipe.")
926 self.assertIn(error_message, browser.contents)
927
754928
755class TestOCIRecipeDeleteView(BaseTestOCIRecipeView):929class TestOCIRecipeDeleteView(BaseTestOCIRecipeView):
756930
@@ -901,6 +1075,8 @@ class TestOCIRecipeView(BaseTestOCIRecipeView):
901 Build file path: Dockerfile1075 Build file path: Dockerfile
902 Build context directory: %s1076 Build context directory: %s
903 Build schedule: Built on request1077 Build schedule: Built on request
1078 Official recipe:
1079 No
904 Latest builds1080 Latest builds
905 Status When complete Architecture1081 Status When complete Architecture
906 Successfully built 30 minutes ago 3861082 Successfully built 30 minutes ago 386
@@ -934,6 +1110,8 @@ class TestOCIRecipeView(BaseTestOCIRecipeView):
934 Build context directory: %s1110 Build context directory: %s
935 Build schedule: Built on request1111 Build schedule: Built on request
936 Build-time\nARG variables: VAR1=123 VAR2=XXX1112 Build-time\nARG variables: VAR1=123 VAR2=XXX
1113 Official recipe:
1114 No
937 Latest builds1115 Latest builds
938 Status When complete Architecture1116 Status When complete Architecture
939 Successfully built 30 minutes ago 3861117 Successfully built 30 minutes ago 386
diff --git a/lib/lp/oci/templates/ocirecipe-index.pt b/lib/lp/oci/templates/ocirecipe-index.pt
index 9a9aefc..a3e67bb 100644
--- a/lib/lp/oci/templates/ocirecipe-index.pt
+++ b/lib/lp/oci/templates/ocirecipe-index.pt
@@ -81,6 +81,13 @@
81 <pre tal:content="build_args" />81 <pre tal:content="build_args" />
82 </dd>82 </dd>
83 </dl>83 </dl>
84 <dl id="official-recipe">
85 <dt>Official recipe:</dt>
86 <dd>
87 <span tal:condition="context/official">Yes</span>
88 <span tal:condition="not: context/official">No</span>
89 </dd>
90 </dl>
84 </div>91 </div>
8592
86 <h2>Latest builds</h2>93 <h2>Latest builds</h2>
diff --git a/lib/lp/oci/templates/ocirecipe-new.pt b/lib/lp/oci/templates/ocirecipe-new.pt
index 1cae71e..00ed4e2 100644
--- a/lib/lp/oci/templates/ocirecipe-new.pt
+++ b/lib/lp/oci/templates/ocirecipe-new.pt
@@ -41,6 +41,9 @@
41 <tal:widget define="widget nocall:view/widgets/processors">41 <tal:widget define="widget nocall:view/widgets/processors">
42 <metal:block use-macro="context/@@launchpad_form/widget_row" />42 <metal:block use-macro="context/@@launchpad_form/widget_row" />
43 </tal:widget>43 </tal:widget>
44 <tal:widget define="widget nocall:view/widgets/official_recipe">
45 <metal:block use-macro="context/@@launchpad_form/widget_row" />
46 </tal:widget>
44 </table>47 </table>
45 </metal:formbody>48 </metal:formbody>
46 </div>49 </div>
diff --git a/lib/lp/oci/tests/test_ocirecipe.py b/lib/lp/oci/tests/test_ocirecipe.py
index 59bd465..1cb69af 100644
--- a/lib/lp/oci/tests/test_ocirecipe.py
+++ b/lib/lp/oci/tests/test_ocirecipe.py
@@ -592,27 +592,27 @@ class TestOCIRecipe(OCIConfigHelperMixin, TestCaseWithFactory):
592 oci_project=oci_project2, registrant=owner, owner=owner)592 oci_project=oci_project2, registrant=owner, owner=owner)
593 for _ in range(2)]593 for _ in range(2)]
594594
595 self.assertIsNone(oci_project1.getOfficialRecipe())595 self.assertTrue(oci_project1.getOfficialRecipes().is_empty())
596 self.assertIsNone(oci_project2.getOfficialRecipe())596 self.assertTrue(oci_project2.getOfficialRecipes().is_empty())
597 for recipe in oci_proj1_recipes + oci_proj2_recipes:597 for recipe in oci_proj1_recipes + oci_proj2_recipes:
598 self.assertFalse(recipe.official)598 self.assertFalse(recipe.official)
599599
600 # Set official for project1 and make sure nothing else got changed.600 # Set official for project1 and make sure nothing else got changed.
601 with StormStatementRecorder() as recorder:601 with StormStatementRecorder() as recorder:
602 oci_project1.setOfficialRecipe(oci_proj1_recipes[0])602 oci_project1.setOfficialRecipeStatus(oci_proj1_recipes[0], True)
603 self.assertEqual(2, recorder.count)603 self.assertEqual(1, recorder.count)
604604
605 self.assertIsNone(oci_project2.getOfficialRecipe())605 self.assertTrue(oci_project2.getOfficialRecipes().is_empty())
606 self.assertEqual(606 self.assertEqual(
607 oci_proj1_recipes[0], oci_project1.getOfficialRecipe())607 oci_proj1_recipes[0], oci_project1.getOfficialRecipes()[0])
608 self.assertTrue(oci_proj1_recipes[0].official)608 self.assertTrue(oci_proj1_recipes[0].official)
609 for recipe in oci_proj1_recipes[1:] + oci_proj2_recipes:609 for recipe in oci_proj1_recipes[1:] + oci_proj2_recipes:
610 self.assertFalse(recipe.official)610 self.assertFalse(recipe.official)
611611
612 # Set back no recipe as official.612 # Set back no recipe as official.
613 with StormStatementRecorder() as recorder:613 with StormStatementRecorder() as recorder:
614 oci_project1.setOfficialRecipe(None)614 oci_project1.setOfficialRecipeStatus(oci_proj1_recipes[0], False)
615 self.assertEqual(1, recorder.count)615 self.assertEqual(0, recorder.count)
616616
617 for recipe in oci_proj1_recipes + oci_proj2_recipes:617 for recipe in oci_proj1_recipes + oci_proj2_recipes:
618 self.assertFalse(recipe.official)618 self.assertFalse(recipe.official)
@@ -630,7 +630,8 @@ class TestOCIRecipe(OCIConfigHelperMixin, TestCaseWithFactory):
630 oci_project=oci_project, registrant=owner)630 oci_project=oci_project, registrant=owner)
631631
632 self.assertRaises(632 self.assertRaises(
633 ValueError, another_oci_project.setOfficialRecipe, recipe)633 ValueError, another_oci_project.setOfficialRecipeStatus,
634 recipe, True)
634635
635 def test_permission_check_on_setOfficialRecipe(self):636 def test_permission_check_on_setOfficialRecipe(self):
636 distro = self.factory.makeDistribution()637 distro = self.factory.makeDistribution()
@@ -642,7 +643,7 @@ class TestOCIRecipe(OCIConfigHelperMixin, TestCaseWithFactory):
642 another_user = self.factory.makePerson()643 another_user = self.factory.makePerson()
643 with person_logged_in(another_user):644 with person_logged_in(another_user):
644 self.assertRaises(645 self.assertRaises(
645 Unauthorized, getattr, oci_project, 'setOfficialRecipe')646 Unauthorized, getattr, oci_project, 'setOfficialRecipeStatus')
646647
647 def test_oci_project_get_recipe_by_name_and_owner(self):648 def test_oci_project_get_recipe_by_name_and_owner(self):
648 owner = self.factory.makePerson()649 owner = self.factory.makePerson()
diff --git a/lib/lp/registry/browser/ociproject.py b/lib/lp/registry/browser/ociproject.py
index a10d9a2..380b94a 100644
--- a/lib/lp/registry/browser/ociproject.py
+++ b/lib/lp/registry/browser/ociproject.py
@@ -234,7 +234,6 @@ class OCIProjectEditView(LaunchpadEditFormView):
234 schema = IOCIProject234 schema = IOCIProject
235 field_names = [235 field_names = [
236 'name',236 'name',
237 'official_recipe',
238 ]237 ]
239238
240 def setUpFields(self):239 def setUpFields(self):
@@ -247,14 +246,6 @@ class OCIProjectEditView(LaunchpadEditFormView):
247 pillar_field = self.form_fields.get(pillar_key).field246 pillar_field = self.form_fields.get(pillar_key).field
248 pillar_field.required = True247 pillar_field.required = True
249248
250 def extendFields(self):
251 official_recipe = self.context.getOfficialRecipe()
252 self.form_fields += form.Fields(
253 Choice(
254 __name__="official_recipe", title=u"Official recipe",
255 required=False, vocabulary="OCIRecipe",
256 default=official_recipe))
257
258 @property249 @property
259 def label(self):250 def label(self):
260 return 'Edit %s OCI project' % self.context.name251 return 'Edit %s OCI project' % self.context.name
@@ -277,9 +268,7 @@ class OCIProjectEditView(LaunchpadEditFormView):
277268
278 @action('Update OCI project', name='update')269 @action('Update OCI project', name='update')
279 def update_action(self, action, data):270 def update_action(self, action, data):
280 official_recipe = data.pop("official_recipe")
281 self.updateContextFromData(data)271 self.updateContextFromData(data)
282 self.context.setOfficialRecipe(official_recipe)
283272
284 @property273 @property
285 def next_url(self):274 def next_url(self):
diff --git a/lib/lp/registry/browser/tests/test_ociproject.py b/lib/lp/registry/browser/tests/test_ociproject.py
index 3743465..a8b4113 100644
--- a/lib/lp/registry/browser/tests/test_ociproject.py
+++ b/lib/lp/registry/browser/tests/test_ociproject.py
@@ -154,11 +154,9 @@ class TestOCIProjectEditView(BrowserTestCase):
154154
155 layer = DatabaseFunctionalLayer155 layer = DatabaseFunctionalLayer
156156
157 def submitEditForm(self, browser, name, official_recipe=''):157 def submitEditForm(self, browser, name):
158 browser.getLink("Edit OCI project").click()158 browser.getLink("Edit OCI project").click()
159 browser.getControl(name="field.name").value = name159 browser.getControl(name="field.name").value = name
160 browser.getControl(name="field.official_recipe").value = (
161 official_recipe)
162 browser.getControl("Update OCI project").click()160 browser.getControl("Update OCI project").click()
163161
164 def test_edit_oci_project(self):162 def test_edit_oci_project(self):
@@ -253,7 +251,7 @@ class TestOCIProjectEditView(BrowserTestCase):
253 view = create_initialized_view(251 view = create_initialized_view(
254 oci_project, name="+edit", principal=oci_project.pillar.owner)252 oci_project, name="+edit", principal=oci_project.pillar.owner)
255 view.update_action.success(253 view.update_action.success(
256 {"name": "changed", "official_recipe": None})254 {"name": "changed"})
257 self.assertSqlAttributeEqualsDate(255 self.assertSqlAttributeEqualsDate(
258 oci_project, "date_last_modified", UTC_NOW)256 oci_project, "date_last_modified", UTC_NOW)
259257
@@ -280,70 +278,6 @@ class TestOCIProjectEditView(BrowserTestCase):
280 extract_text(find_tags_by_class(browser.contents, "message")[1]),278 extract_text(find_tags_by_class(browser.contents, "message")[1]),
281 "Invalid name 'invalid name'.")279 "Invalid name 'invalid name'.")
282280
283 def test_edit_oci_project_setting_official_recipe(self):
284 self.useFixture(FeatureFixture({OCI_RECIPE_ALLOW_CREATE: 'on'}))
285
286 with admin_logged_in():
287 oci_project = self.factory.makeOCIProject()
288 user = oci_project.pillar.owner
289 recipe1 = self.factory.makeOCIRecipe(
290 registrant=user, owner=user, oci_project=oci_project)
291 recipe2 = self.factory.makeOCIRecipe(
292 registrant=user, owner=user, oci_project=oci_project)
293
294 name_value = oci_project.name
295 recipe_value = "%s/%s" % (user.name, recipe1.name)
296
297 browser = self.getViewBrowser(oci_project, user=user)
298 self.submitEditForm(browser, name_value, recipe_value)
299
300 with admin_logged_in():
301 self.assertEqual(recipe1, oci_project.getOfficialRecipe())
302 self.assertTrue(recipe1.official)
303 self.assertFalse(recipe2.official)
304
305 def test_edit_oci_project_overriding_official_recipe(self):
306 self.useFixture(FeatureFixture({OCI_RECIPE_ALLOW_CREATE: 'on'}))
307 with admin_logged_in():
308 oci_project = self.factory.makeOCIProject()
309 user = oci_project.pillar.owner
310 recipe1 = self.factory.makeOCIRecipe(
311 registrant=user, owner=user, oci_project=oci_project)
312 recipe2 = self.factory.makeOCIRecipe(
313 registrant=user, owner=user, oci_project=oci_project)
314
315 # Sets recipe1 as the current official one
316 oci_project.setOfficialRecipe(recipe1)
317
318 # And we will try to set recipe2 as the new official.
319 name_value = oci_project.name
320 recipe_value = "%s/%s" % (user.name, recipe2.name)
321
322 browser = self.getViewBrowser(oci_project, user=user)
323 self.submitEditForm(browser, name_value, recipe_value)
324
325 with admin_logged_in():
326 self.assertEqual(recipe2, oci_project.getOfficialRecipe())
327 self.assertFalse(recipe1.official)
328 self.assertTrue(recipe2.official)
329
330 def test_edit_oci_project_unsetting_official_recipe(self):
331 self.useFixture(FeatureFixture({OCI_RECIPE_ALLOW_CREATE: 'on'}))
332 with admin_logged_in():
333 oci_project = self.factory.makeOCIProject()
334 user = oci_project.pillar.owner
335 recipe = self.factory.makeOCIRecipe(
336 registrant=user, owner=user, oci_project=oci_project)
337 oci_project.setOfficialRecipe(recipe)
338 name_value = oci_project.name
339
340 browser = self.getViewBrowser(oci_project, user=user)
341 self.submitEditForm(browser, name_value, '')
342
343 with admin_logged_in():
344 self.assertEqual(None, oci_project.getOfficialRecipe())
345 self.assertFalse(recipe.official)
346
347281
348class TestOCIProjectAddView(BrowserTestCase):282class TestOCIProjectAddView(BrowserTestCase):
349283
diff --git a/lib/lp/registry/interfaces/ociproject.py b/lib/lp/registry/interfaces/ociproject.py
index cf4abef..718b9d7 100644
--- a/lib/lp/registry/interfaces/ociproject.py
+++ b/lib/lp/registry/interfaces/ociproject.py
@@ -95,8 +95,8 @@ class IOCIProjectView(IHasGitRepositories, Interface):
95 def searchRecipes(query):95 def searchRecipes(query):
96 """Searches for recipes in this OCI project."""96 """Searches for recipes in this OCI project."""
9797
98 def getOfficialRecipe():98 def getOfficialRecipes():
99 """Gets the official recipe for this OCI project."""99 """Gets the official recipes for this OCI project."""
100100
101 def getDefaultGitRepository(person):101 def getDefaultGitRepository(person):
102 """Returns the default git repository for the given user under the102 """Returns the default git repository for the given user under the
@@ -147,9 +147,8 @@ class IOCIProjectEdit(Interface):
147 status=SeriesStatus.DEVELOPMENT, date_created=DEFAULT):147 status=SeriesStatus.DEVELOPMENT, date_created=DEFAULT):
148 """Creates a new `IOCIProjectSeries`."""148 """Creates a new `IOCIProjectSeries`."""
149149
150 def setOfficialRecipe(recipe):150 def setOfficialRecipeStatus(recipe, status):
151 """Sets the given recipe as the official one. If recipe is None,151 """Change whether an OCI Recipe is official or not for this project."""
152 the current official recipe will be unset."""
153152
154153
155class IOCIProjectLegitimate(Interface):154class IOCIProjectLegitimate(Interface):
diff --git a/lib/lp/registry/model/ociproject.py b/lib/lp/registry/model/ociproject.py
index 4526724..46ea7da 100644
--- a/lib/lp/registry/model/ociproject.py
+++ b/lib/lp/registry/model/ociproject.py
@@ -200,12 +200,12 @@ class OCIProject(BugTargetBase, StormBase):
200 Person.name.contains_string(query))200 Person.name.contains_string(query))
201 return q.order_by(Person.name, OCIRecipe.name)201 return q.order_by(Person.name, OCIRecipe.name)
202202
203 def getOfficialRecipe(self):203 def getOfficialRecipes(self):
204 """See `IOCIProject`."""204 """See `IOCIProject`."""
205 from lp.oci.model.ocirecipe import OCIRecipe205 from lp.oci.model.ocirecipe import OCIRecipe
206 return self.getRecipes().find(OCIRecipe._official == True).one()206 return self.getRecipes().find(OCIRecipe._official == True)
207207
208 def setOfficialRecipe(self, recipe):208 def setOfficialRecipeStatus(self, recipe, status):
209 """See `IOCIProject`."""209 """See `IOCIProject`."""
210 if recipe is not None and recipe.oci_project != self:210 if recipe is not None and recipe.oci_project != self:
211 raise ValueError(211 raise ValueError(
@@ -215,12 +215,7 @@ class OCIProject(BugTargetBase, StormBase):
215 # attribute not declared on the Interface, and we need to set it215 # attribute not declared on the Interface, and we need to set it
216 # regardless of security checks on OCIRecipe objects.216 # regardless of security checks on OCIRecipe objects.
217 recipe = removeSecurityProxy(recipe)217 recipe = removeSecurityProxy(recipe)
218 previous = removeSecurityProxy(self.getOfficialRecipe())218 recipe._official = status
219 if previous != recipe:
220 if previous is not None:
221 previous._official = False
222 if recipe is not None:
223 recipe._official = True
224219
225 def getDefaultGitRepository(self, person):220 def getDefaultGitRepository(self, person):
226 namespace = getUtility(IGitNamespaceSet).get(person, oci_project=self)221 namespace = getUtility(IGitNamespaceSet).get(person, oci_project=self)

Subscribers

People subscribed via source and target branches

to status/vote changes: