← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~ilasc/launchpad:ui-create-oci-project into launchpad:master

 

Ioana Lasc has proposed merging ~ilasc/launchpad:ui-create-oci-project into launchpad:master.

Commit message:
Add Page for OCI Project Creation

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~ilasc/launchpad/+git/launchpad/+merge/381743

Added a functional page to LP that allows the user to enter
an OCI Project Name and create/edit the project.
This is protected by the OCI_PROJECT_ALLOW_CREATE feature flag
for now, until MP 381321 lands and we can move to
oci_project_admin permissions.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~ilasc/launchpad:ui-create-oci-project into launchpad:master.
diff --git a/lib/lp/registry/browser/configure.zcml b/lib/lp/registry/browser/configure.zcml
index 5605a9f..ff35646 100644
--- a/lib/lp/registry/browser/configure.zcml
+++ b/lib/lp/registry/browser/configure.zcml
@@ -2149,6 +2149,13 @@
         template="../templates/distribution-newmirror.pt">
     </browser:page>
     <browser:page
+        name="+newociproject"
+        for="lp.registry.interfaces.distribution.IDistribution"
+        class="lp.registry.browser.ociproject.OCIProjectAddView"
+        permission="launchpad.AnyPerson"
+        template="../templates/ociproject-new.pt"
+        />
+    <browser:page
         name="+search"
         for="lp.registry.interfaces.distribution.IDistribution"
         class="lp.registry.browser.distribution.DistributionPackageSearchView"
diff --git a/lib/lp/registry/browser/distribution.py b/lib/lp/registry/browser/distribution.py
index a17ff8d..82e2de2 100644
--- a/lib/lp/registry/browser/distribution.py
+++ b/lib/lp/registry/browser/distribution.py
@@ -306,11 +306,15 @@ class DistributionNavigationMenu(NavigationMenu, DistributionLinksMixin):
     def sharing(self):
         return Link('+sharing', 'Sharing', icon='edit')
 
+    def newociproject(self):
+        text = 'Create an OCI Project'
+        return Link('+newociproject', text, icon='add')
+
     @cachedproperty
     def links(self):
         return [
             'edit', 'admin', 'pubconf', 'subscribe_to_bug_mail',
-            'edit_bug_mail', 'sharing']
+            'edit_bug_mail', 'sharing', 'newociproject']
 
 
 class DistributionOverviewMenu(ApplicationMenu, DistributionLinksMixin):
diff --git a/lib/lp/registry/browser/ociproject.py b/lib/lp/registry/browser/ociproject.py
index 5ee4205..f9136b9 100644
--- a/lib/lp/registry/browser/ociproject.py
+++ b/lib/lp/registry/browser/ociproject.py
@@ -20,15 +20,24 @@ from zope.interface import implementer
 from lp.app.browser.launchpadform import (
     action,
     LaunchpadEditFormView,
+    LaunchpadFormView,
     )
 from lp.app.browser.tales import CustomizableFormatter
 from lp.app.errors import NotFoundError
 from lp.code.browser.vcslisting import TargetDefaultVCSNavigationMixin
 from lp.oci.interfaces.ocirecipe import IOCIRecipeSet
+from lp.registry.errors import NoSuchOCIProjectName
 from lp.registry.interfaces.ociproject import (
     IOCIProject,
     IOCIProjectSet,
+    OCI_PROJECT_ALLOW_CREATE,
+    OCIProjectCreateFeatureDisabled,
     )
+from lp.registry.interfaces.ociprojectname import (
+    IOCIProjectName,
+    IOCIProjectNameSet,
+    )
+from lp.services.features import getFeatureFlag
 from lp.services.webapp import (
     canonical_url,
     ContextMenu,
@@ -43,6 +52,41 @@ from lp.services.webapp.breadcrumb import Breadcrumb
 from lp.services.webapp.interfaces import IMultiFacetedBreadcrumb
 
 
+class OCIProjectAddView(LaunchpadFormView):
+
+    schema = IOCIProjectName
+    field_names = ['name']
+
+    def initialize(self):
+        if not getFeatureFlag(OCI_PROJECT_ALLOW_CREATE):
+            raise OCIProjectCreateFeatureDisabled
+        super(OCIProjectAddView, self).initialize()
+
+    @action("Create Project", name="create")
+    def create_action(self, action, data):
+        """Create a new OCI Project."""
+        name = data.get('name')
+        try:
+            oci_project_name = getUtility(IOCIProjectNameSet).getByName(name)
+        except NoSuchOCIProjectName:
+            oci_project_name = getUtility(IOCIProjectNameSet).new(name)
+
+        oci_project = getUtility(IOCIProjectSet).getByDistributionAndName(
+            self.context, oci_project_name.name)
+
+        if oci_project:
+            self.setFieldError(
+                    'name',
+                    'There is already an OCI project in %s with this name.' % (
+                        self.context.display_name))
+        else:
+            oci_project = getUtility(IOCIProjectSet).new(
+                registrant=self.user,
+                pillar=self.context,
+                name=oci_project_name)
+            self.next_url = canonical_url(oci_project)
+
+
 class OCIProjectFormatterAPI(CustomizableFormatter):
     """Adapt `IOCIProject` objects to a formatted string."""
 
diff --git a/lib/lp/registry/browser/tests/test_ociproject.py b/lib/lp/registry/browser/tests/test_ociproject.py
index 57f1061..ceb82b7 100644
--- a/lib/lp/registry/browser/tests/test_ociproject.py
+++ b/lib/lp/registry/browser/tests/test_ociproject.py
@@ -12,7 +12,9 @@ from datetime import datetime
 
 import pytz
 
+from lp.registry.interfaces.ociproject import OCI_PROJECT_ALLOW_CREATE
 from lp.services.database.constants import UTC_NOW
+from lp.services.features.testing import FeatureFixture
 from lp.services.webapp import canonical_url
 from lp.services.webapp.escaping import structured
 from lp.testing import (
@@ -150,3 +152,48 @@ class TestOCIProjectEditView(BrowserTestCase):
         self.assertStartsWith(
             extract_text(find_tags_by_class(browser.contents, "message")[1]),
             "Invalid name 'invalid name'.")
+
+
+class TestOCIProjectAddView(BrowserTestCase):
+
+    layer = DatabaseFunctionalLayer
+
+    def setUp(self):
+        super(TestOCIProjectAddView, self).setUp()
+        self.useFixture(FeatureFixture({OCI_PROJECT_ALLOW_CREATE: "on"}))
+
+    def test_create_oci_project(self):
+        oci_project = self.factory.makeOCIProject()
+        new_distribution = self.factory.makeDistribution(
+            owner=oci_project.pillar.owner)
+        browser = self.getViewBrowser(
+            new_distribution, view_name='+newociproject')
+        browser.getControl(name="field.name").value = "new-name"
+        browser.getControl("Create Project").click()
+
+        content = find_main_content(browser.contents)
+        self.assertEqual(
+            "OCI project new-name for %s" % new_distribution.display_name,
+            extract_text(content.h1))
+        self.assertThat(
+            "Distribution:\n%s\n" % (
+                new_distribution.display_name),
+            MatchesTagText(content, "distribution"))
+        self.assertThat(
+             "Name:\nnew-name\n",
+             MatchesTagText(content, "name"))
+
+    def test_create_oci_project_already_exists(self):
+        distribution = self.factory.makeDistribution()
+        self.factory.makeOCIProject(ociprojectname="new-name",
+                                    pillar=distribution)
+
+        browser = self.getViewBrowser(
+            distribution, view_name='+newociproject')
+        browser.getControl(name="field.name").value = "new-name"
+        browser.getControl("Create Project").click()
+
+        self.assertEqual(
+            "There is already an OCI project in %s with this name." % (
+                distribution.display_name),
+            extract_text(find_tags_by_class(browser.contents, "message")[1]))
diff --git a/lib/lp/registry/interfaces/ociproject.py b/lib/lp/registry/interfaces/ociproject.py
index 8a3e734..e7567e7 100644
--- a/lib/lp/registry/interfaces/ociproject.py
+++ b/lib/lp/registry/interfaces/ociproject.py
@@ -9,11 +9,13 @@ __metaclass__ = type
 __all__ = [
     'IOCIProject',
     'IOCIProjectSet',
-    'OCI_PROJECT_ALLOW_CREATE'
+    'OCI_PROJECT_ALLOW_CREATE',
+    'OCIProjectCreateFeatureDisabled'
     ]
 
 from lazr.restful.declarations import (
     call_with,
+    error_status,
     export_as_webservice_entry,
     export_factory_operation,
     exported,
@@ -26,7 +28,7 @@ from lazr.restful.fields import (
     Reference,
     ReferenceChoice,
     )
-from lp.app.validators.path import path_does_not_escape
+from six.moves import http_client
 from zope.interface import Interface
 from zope.schema import (
     Bool,
@@ -35,9 +37,11 @@ from zope.schema import (
     Text,
     TextLine,
     )
+from zope.security.interfaces import Unauthorized
 
 from lp import _
 from lp.app.validators.name import name_validator
+from lp.app.validators.path import path_does_not_escape
 from lp.bugs.interfaces.bugtarget import IBugTarget
 from lp.code.interfaces.gitref import IGitRef
 from lp.code.interfaces.hasgitrepositories import IHasGitRepositories
@@ -170,3 +174,12 @@ class IOCIProjectSet(Interface):
 
     def getByDistributionAndName(distribution, name):
         """Get the OCIProjects for a given distribution."""
+
+
+@error_status(http_client.UNAUTHORIZED)
+class OCIProjectCreateFeatureDisabled(Unauthorized):
+    """Only certain users can create new OCI Projects."""
+
+    def __init__(self):
+        super(OCIProjectCreateFeatureDisabled, self).__init__(
+            "You do not have permission to create an OCI Project.")
diff --git a/lib/lp/registry/templates/ociproject-new.pt b/lib/lp/registry/templates/ociproject-new.pt
new file mode 100644
index 0000000..321bf4b
--- /dev/null
+++ b/lib/lp/registry/templates/ociproject-new.pt
@@ -0,0 +1,15 @@
+<html
+  xmlns="http://www.w3.org/1999/xhtml";
+  xmlns:tal="http://xml.zope.org/namespaces/tal";
+  xmlns:metal="http://xml.zope.org/namespaces/metal";
+  xmlns:i18n="http://xml.zope.org/namespaces/i18n";
+  metal:use-macro="view/macro:page/main_only"
+  i18n:domain="launchpad">
+
+  <body>
+  <div metal:fill-slot="main">
+      <p>Enter the name for the new OCI Project.</p>
+      <div metal:use-macro="context/@@launchpad_form/form" />
+    </div>
+  </body>
+</html>
\ No newline at end of file