launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #24760
[Merge] ~pappacena/launchpad:ui-create-ociproject-for-projects into launchpad:master
Thiago F. Pappacena has proposed merging ~pappacena/launchpad:ui-create-ociproject-for-projects into launchpad:master with ~pappacena/launchpad:gitrepo-ociproject-refactoring as a prerequisite.
Commit message:
UI to allow creating and editing OCI projects based on projects.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~pappacena/launchpad/+git/launchpad/+merge/384506
--
Your team Launchpad code reviewers is requested to review the proposed merge of ~pappacena/launchpad:ui-create-ociproject-for-projects into launchpad:master.
diff --git a/lib/lp/code/browser/tests/test_product.py b/lib/lp/code/browser/tests/test_product.py
index 360745e..d33a85e 100644
--- a/lib/lp/code/browser/tests/test_product.py
+++ b/lib/lp/code/browser/tests/test_product.py
@@ -426,3 +426,17 @@ class TestCanConfigureBranches(TestCaseWithFactory):
login_person(product.owner)
view = create_view(product, '+branches')
self.assertTrue(view.can_configure_branches())
+
+
+class TestProductOverviewOCIProject(TestCaseWithFactory):
+
+ layer = DatabaseFunctionalLayer
+
+ def test_displays_create_oci_project_link(self):
+ product = self.factory.makeProduct()
+
+ browser = self.getUserBrowser(
+ canonical_url(product), user=product.owner)
+ text = extract_text(find_tag_by_id(browser.contents, 'global-actions'))
+
+ self.assertIn("Create an OCI Project", text)
diff --git a/lib/lp/registry/browser/configure.zcml b/lib/lp/registry/browser/configure.zcml
index b8dc1bb..949e074 100644
--- a/lib/lp/registry/browser/configure.zcml
+++ b/lib/lp/registry/browser/configure.zcml
@@ -2156,6 +2156,13 @@
template="../templates/ociproject-new.pt"
/>
<browser:page
+ name="+new-oci-project"
+ for="lp.registry.interfaces.product.IProduct"
+ class="lp.registry.browser.ociproject.OCIProjectAddView"
+ permission="launchpad.AnyPerson"
+ template="../templates/ociproject-new.pt"
+ />
+ <browser:page
name="+search-oci-project"
for="lp.registry.interfaces.distribution.IDistribution"
class="lp.registry.browser.distribution.DistributionOCIProjectSearchView"
diff --git a/lib/lp/registry/browser/ociproject.py b/lib/lp/registry/browser/ociproject.py
index df7ac85..6ca8786 100644
--- a/lib/lp/registry/browser/ociproject.py
+++ b/lib/lp/registry/browser/ociproject.py
@@ -14,6 +14,8 @@ __all__ = [
'OCIProjectNavigationMenu',
]
+from lp.registry.interfaces.distribution import IDistribution
+from lp.registry.interfaces.product import IProduct
from zope.component import getUtility
from zope.formlib import form
from zope.interface import implementer
@@ -170,11 +172,18 @@ class OCIProjectEditView(LaunchpadEditFormView):
schema = IOCIProject
field_names = [
- 'distribution',
'name',
'official_recipe',
]
+ def setUpFields(self):
+ pillar = self.context.pillar
+ if IDistribution.providedBy(pillar):
+ self.field_names = ['distribution'] + self.field_names
+ elif IProduct.providedBy(pillar):
+ self.field_names = ['project'] + self.field_names
+ super(OCIProjectEditView, self).setUpFields()
+
def extendFields(self):
official_recipe = self.context.getOfficialRecipe()
self.form_fields += form.Fields(
diff --git a/lib/lp/registry/browser/product.py b/lib/lp/registry/browser/product.py
index c059dbb..e08a986 100644
--- a/lib/lp/registry/browser/product.py
+++ b/lib/lp/registry/browser/product.py
@@ -1,4 +1,4 @@
-# Copyright 2009-2018 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2020 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Browser views for products."""
@@ -275,6 +275,10 @@ class ProductNavigation(
def traverse_commercialsubscription(self, name):
return self.context.commercial_subscription
+ @stepthrough('+oci')
+ def traverse_oci(self, name):
+ return self.context.getOCIProject(name)
+
def traverse(self, name):
return self.context.getSeries(name)
@@ -426,6 +430,7 @@ class ProductNavigationMenu(NavigationMenu):
facet = 'overview'
links = [
'details',
+ 'new_oci_project',
'announcements',
'downloads',
]
@@ -499,6 +504,11 @@ class ProductEditLinksMixin(StructuralSubscriptionMenuMixin):
def sharing(self):
return Link('+sharing', 'Sharing', icon='edit')
+ @enabled_with_permission('launchpad.Driver')
+ def new_oci_project(self):
+ text = 'Create an OCI Project'
+ return Link('+new-oci-project', text, icon='add')
+
class IProductEditMenu(Interface):
"""A marker interface for the 'Change details' navigation menu."""
@@ -517,7 +527,8 @@ class ProductActionNavigationMenu(NavigationMenu, ProductEditLinksMixin):
@cachedproperty
def links(self):
- links = ['edit', 'review_license', 'administer', 'sharing']
+ links = ['edit', 'review_license', 'administer', 'sharing',
+ 'new_oci_project']
add_subscribe_link(links)
return links
diff --git a/lib/lp/registry/browser/tests/test_ociproject.py b/lib/lp/registry/browser/tests/test_ociproject.py
index 5187cd6..c9aaa77 100644
--- a/lib/lp/registry/browser/tests/test_ociproject.py
+++ b/lib/lp/registry/browser/tests/test_ociproject.py
@@ -79,7 +79,7 @@ class TestOCIProjectView(BrowserTestCase):
layer = DatabaseFunctionalLayer
- def test_index(self):
+ def test_index_distribution_pillar(self):
distribution = self.factory.makeDistribution(displayname="My Distro")
oci_project = self.factory.makeOCIProject(
pillar=distribution, ociprojectname="oci-name")
@@ -91,6 +91,18 @@ class TestOCIProjectView(BrowserTestCase):
Name: oci-name
""", self.getMainText(oci_project))
+ def test_index_project_pillar(self):
+ product = self.factory.makeProduct(displayname="My Project")
+ oci_project = self.factory.makeOCIProject(
+ pillar=product, ociprojectname="oci-name")
+ self.assertTextMatchesExpressionIgnoreWhitespace("""\
+ OCI project oci-name for My Project
+ .*
+ OCI project information
+ Project: My Project
+ Name: oci-name
+ """, self.getMainText(oci_project))
+
class TestOCIProjectEditView(BrowserTestCase):
@@ -123,11 +135,39 @@ class TestOCIProjectEditView(BrowserTestCase):
self.assertThat(
"Distribution:\n%s\nEdit OCI project" % (
new_distribution.display_name),
- MatchesTagText(content, "distribution"))
+ MatchesTagText(content, "pillar"))
self.assertThat(
"Name:\nnew-name\nEdit OCI project",
MatchesTagText(content, "name"))
+ def test_edit_oci_project_change_project_pillar(self):
+ with admin_logged_in():
+ owner = self.factory.makePerson()
+ project = self.factory.makeProduct(owner=owner)
+ new_project = self.factory.makeProduct(owner=owner)
+ oci_project = self.factory.makeOCIProject(pillar=project)
+ new_project_name = new_project.name
+
+ browser = self.getViewBrowser(
+ oci_project, user=oci_project.pillar.owner)
+ browser.getLink("Edit OCI project").click()
+ browser.getControl(name="field.project").value = [new_project_name]
+ browser.getControl(name="field.name").value = "new-name"
+ browser.getControl("Update OCI project").click()
+
+ content = find_main_content(browser.contents)
+ with person_logged_in(owner):
+ self.assertEqual(
+ "OCI project new-name for %s" % new_project.display_name,
+ extract_text(content.h1))
+ self.assertThat(
+ "Project:\n%s\nEdit OCI project" % (
+ new_project.display_name),
+ MatchesTagText(content, "pillar"))
+ self.assertThat(
+ "Name:\nnew-name\nEdit OCI project",
+ MatchesTagText(content, "name"))
+
def test_edit_oci_project_ad_oci_project_admin(self):
admin_person = self.factory.makePerson()
admin_team = self.factory.makeTeam(members=[admin_person])
@@ -153,7 +193,7 @@ class TestOCIProjectEditView(BrowserTestCase):
self.assertThat(
"Distribution:\n%s\nEdit OCI project" % (
new_distribution.display_name),
- MatchesTagText(content, "distribution"))
+ MatchesTagText(content, "pillar"))
self.assertThat(
"Name:\nnew-name\nEdit OCI project",
MatchesTagText(content, "name"))
@@ -280,11 +320,33 @@ class TestOCIProjectAddView(BrowserTestCase):
self.assertThat(
"Distribution:\n%s\nEdit OCI project" % (
new_distribution.display_name),
- MatchesTagText(content, "distribution"))
+ MatchesTagText(content, "pillar"))
self.assertThat(
"Name:\nnew-name\nEdit OCI project",
MatchesTagText(content, "name"))
+ def test_create_oci_project_for_project(self):
+ oci_project = self.factory.makeOCIProject()
+ user = oci_project.pillar.owner
+ project = self.factory.makeProduct(owner=user)
+ browser = self.getViewBrowser(
+ project, user=user, view_name='+new-oci-project')
+ browser.getControl(name="field.name").value = "new-name"
+ browser.getControl("Create OCI Project").click()
+
+ content = find_main_content(browser.contents)
+ with person_logged_in(user):
+ self.assertEqual(
+ "OCI project new-name for %s" % project.display_name,
+ extract_text(content.h1))
+ self.assertThat(
+ "Project:\n%s\nEdit OCI project" % (
+ project.display_name),
+ MatchesTagText(content, "pillar"))
+ self.assertThat(
+ "Name:\nnew-name\nEdit OCI project",
+ MatchesTagText(content, "name"))
+
def test_create_oci_project_already_exists(self):
person = self.factory.makePerson()
distribution = self.factory.makeDistribution(oci_project_admin=person)
diff --git a/lib/lp/registry/interfaces/product.py b/lib/lp/registry/interfaces/product.py
index 6b3df06..55091a1 100644
--- a/lib/lp/registry/interfaces/product.py
+++ b/lib/lp/registry/interfaces/product.py
@@ -1,4 +1,4 @@
-# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2020 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Interfaces including and related to IProduct."""
@@ -798,6 +798,14 @@ class IProductView(
filter_statuses.
"""
+ def canAdministerOCIProjects(person):
+ """Checks if the given person can manage OCI projects for this
+ Product."""
+
+ def getOCIProject(name):
+ """Return a `OCIProject` with the given name for this product, or None.
+ """
+
def getPackage(distroseries):
"""Return a package in that distroseries for this product."""
diff --git a/lib/lp/registry/model/product.py b/lib/lp/registry/model/product.py
index a99d846..570f097 100644
--- a/lib/lp/registry/model/product.py
+++ b/lib/lp/registry/model/product.py
@@ -1,4 +1,4 @@
-# Copyright 2009-2018 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2020 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Database classes including and related to Product."""
@@ -142,6 +142,7 @@ from lp.registry.interfaces.accesspolicy import (
IAccessPolicyGrantSource,
IAccessPolicySource,
)
+from lp.registry.interfaces.ociproject import IOCIProjectSet
from lp.registry.interfaces.oopsreferences import IHasOOPSReferences
from lp.registry.interfaces.person import (
IPersonSet,
@@ -1140,6 +1141,23 @@ class Product(SQLBase, BugTargetBase, MakesAnnouncements,
"""See `IBugTarget`."""
return self.name
+ def getOCIProject(self, name):
+ return getUtility(IOCIProjectSet).getByPillarAndName(
+ self, name)
+
+ def canAdministerOCIProjects(self, person):
+ if person is None:
+ return False
+ # XXX: pappacena 2020-05-25: Maybe we should have a
+ # oci_project_admin on product too, the same way we have on
+ # Distribution.
+ if person.inTeam(self.driver):
+ return True
+ person_roles = IPersonRoles(person)
+ if person_roles.in_admin or person_roles.isOwner(self):
+ return True
+ return False
+
def getPackage(self, distroseries):
"""See `IProduct`."""
if isinstance(distroseries, Distribution):
diff --git a/lib/lp/registry/templates/ociproject-index.pt b/lib/lp/registry/templates/ociproject-index.pt
index 427b383..9a14cb7 100644
--- a/lib/lp/registry/templates/ociproject-index.pt
+++ b/lib/lp/registry/templates/ociproject-index.pt
@@ -28,11 +28,14 @@
<div metal:fill-slot="main">
<h2>OCI project information</h2>
<div class="two-column-list">
- <dl id="distribution" tal:define="distribution context/distribution">
- <dt>Distribution:</dt>
+ <dl id="pillar" tal:define="pillar context/pillar">
+ <dt>
+ <span tal:condition="context/distribution">Distribution:</span>
+ <span tal:condition="context/project">Project:</span>
+ </dt>
<dd>
- <a tal:attributes="href distribution/fmt:url"
- tal:content="distribution/display_name"/>
+ <a tal:attributes="href pillar/fmt:url"
+ tal:content="pillar/display_name"/>
<a tal:replace="structure context/menu:overview/edit/fmt:icon"/>
</dd>
</dl>
diff --git a/lib/lp/security.py b/lib/lp/security.py
index f7d7bf8..dfc06c2 100644
--- a/lib/lp/security.py
+++ b/lib/lp/security.py
@@ -3464,7 +3464,7 @@ class EditOCIProject(AuthorizationBase):
"""Maintainers, drivers, and admins can drive projects."""
return (user.in_admin or
user.isDriver(self.obj.pillar) or
- user.inTeam(self.obj.pillar.oci_project_admin))
+ self.obj.pillar.canAdministerOCIProjects(user))
class EditOCIProjectSeries(DelegatedAuthorization):
References