← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~pappacena/launchpad:ociproject-picker into launchpad:master

 

Thiago F. Pappacena has proposed merging ~pappacena/launchpad:ociproject-picker into launchpad:master.

Commit message:
Adding OCIProject vocabulary, to support OCIProject picker widgets in forms

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~pappacena/launchpad/+git/launchpad/+merge/393627
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~pappacena/launchpad:ociproject-picker into launchpad:master.
diff --git a/lib/lp/registry/interfaces/ociproject.py b/lib/lp/registry/interfaces/ociproject.py
index e72a4cf..75bc348 100644
--- a/lib/lp/registry/interfaces/ociproject.py
+++ b/lib/lp/registry/interfaces/ociproject.py
@@ -204,7 +204,8 @@ class IOCIProjectSet(Interface):
     def getByPillarAndName(pillar, name):
         """Get the OCIProjects for a given distribution or project.
 
-        :param pillar: An instance of Distribution or Product.
+        :param pillar: An instance of Distribution or Product, or the
+            respective pillar name.
         :param name: The OCIProject name to find.
         :return: The OCIProject found.
         """
@@ -213,6 +214,9 @@ class IOCIProjectSet(Interface):
         """Find OCIProjects for a given pillar that contain the provided
         name."""
 
+    def findByName(name_substring):
+        """Find OCIProjects that contain the provided name."""
+
     def preloadDataForOCIProjects(oci_projects):
         """Preload data for the given list of OCIProject objects."""
 
diff --git a/lib/lp/registry/model/ociproject.py b/lib/lp/registry/model/ociproject.py
index c27f2c6..bef4aa4 100644
--- a/lib/lp/registry/model/ociproject.py
+++ b/lib/lp/registry/model/ociproject.py
@@ -12,7 +12,13 @@ __all__ = [
     ]
 
 import pytz
+import six
 from six import text_type
+from storm.expr import (
+    Join,
+    LeftJoin,
+    Or,
+    )
 from storm.locals import (
     Bool,
     DateTime,
@@ -263,12 +269,34 @@ class OCIProjectSet:
 
     def getByPillarAndName(self, pillar, name):
         """See `IOCIProjectSet`."""
-        target = IStore(OCIProject).find(
-            OCIProject,
-            self._get_pillar_attribute(pillar) == pillar,
-            OCIProject.ociprojectname == OCIProjectName.id,
-            OCIProjectName.name == name).one()
-        return target
+        from lp.registry.model.product import Product
+        from lp.registry.model.distribution import Distribution
+
+        # If pillar is not an string, we expect it to be either an
+        # IDistribution or IProduct.
+        if not isinstance(pillar, six.string_types):
+            return IStore(OCIProject).find(
+                OCIProject,
+                self._get_pillar_attribute(pillar) == pillar,
+                OCIProject.ociprojectname == OCIProjectName.id,
+                OCIProjectName.name == name).one()
+        else:
+            # If we got a pillar name instead, we need to join with both
+            # Distribution and Product tables, to find out which one has the
+            # provided name.
+            tables = [
+                OCIProject,
+                Join(OCIProjectName,
+                     OCIProject.ociprojectname == OCIProjectName.id),
+                LeftJoin(Distribution,
+                         OCIProject.distribution == Distribution.id),
+                LeftJoin(Product,
+                         OCIProject.project == Product.id)
+            ]
+            return IStore(OCIProject).using(*tables).find(
+                OCIProject,
+                Or(Distribution.name == pillar, Product.name == pillar),
+                OCIProjectName.name == name).one()
 
     def findByPillarAndName(self, pillar, name_substring):
         """See `IOCIProjectSet`."""
@@ -278,6 +306,12 @@ class OCIProjectSet:
             OCIProject.ociprojectname == OCIProjectName.id,
             OCIProjectName.name.contains_string(name_substring))
 
+    def findByName(self, name_substring):
+        return IStore(OCIProject).find(
+            OCIProject,
+            OCIProject.ociprojectname == OCIProjectName.id,
+            OCIProjectName.name.contains_string(name_substring))
+
     def preloadDataForOCIProjects(self, oci_projects):
         """See `IOCIProjectSet`."""
         oci_projects = [removeSecurityProxy(i) for i in oci_projects]
diff --git a/lib/lp/registry/tests/test_ociproject.py b/lib/lp/registry/tests/test_ociproject.py
index c01f939..0232b2e 100644
--- a/lib/lp/registry/tests/test_ociproject.py
+++ b/lib/lp/registry/tests/test_ociproject.py
@@ -16,6 +16,7 @@ from testtools.matchers import (
     )
 from testtools.testcase import ExpectedException
 from zope.component import getUtility
+from zope.schema.vocabulary import getVocabularyRegistry
 from zope.security.interfaces import Unauthorized
 from zope.security.proxy import removeSecurityProxy
 
@@ -313,3 +314,54 @@ class TestOCIProjectWebservice(TestCaseWithFactory):
                 owner=other_user))
 
         self.assertCanCreateOCIProject(distro, self.person)
+
+
+class TestOCIProjectVocabulary(TestCaseWithFactory):
+    layer = DatabaseFunctionalLayer
+
+    def createOCIProjects(self, name_tpl="my-ociproject-%s", count=5):
+        return [self.factory.makeOCIProject(ociprojectname=name_tpl % i)
+                for i in range(count)]
+
+    def getVocabulary(self, context=None):
+        vocabulary_registry = getVocabularyRegistry()
+        return vocabulary_registry.get(context, "OCIProject")
+
+    def assertContainsSameOCIProjects(self, ociprojects, search_result):
+        """Asserts that the search result contains only the given list of OCI
+        projects.
+        """
+        naked = removeSecurityProxy
+        self.assertEqual(
+            set(naked(ociproject).id for ociproject in ociprojects),
+            set(naked(term.value).id for term in search_result))
+
+    def test_search_with_name_substring(self):
+        vocabulary = self.getVocabulary()
+        projects = self.createOCIProjects("test-project-%s", 10)
+        self.createOCIProjects("another-pattern-%s", 10)
+
+        search_result = vocabulary.searchForTerms("test-project")
+        self.assertContainsSameOCIProjects(projects, search_result)
+
+    def test_search_without_name_substring(self):
+        vocabulary = self.getVocabulary()
+        projects = self.createOCIProjects()
+        search_result = vocabulary.searchForTerms("")
+        self.assertContainsSameOCIProjects(projects, search_result)
+
+    def test_to_term(self):
+        vocabulary = self.getVocabulary()
+        ociproject = self.factory.makeOCIProject()
+        term = removeSecurityProxy(vocabulary).toTerm(ociproject)
+
+        expected_token = "%s/%s" % (ociproject.pillar.name, ociproject.name)
+        self.assertEqual(expected_token, term.title)
+        self.assertEqual(expected_token, term.token)
+
+    def test_getTermByToken(self):
+        vocabulary = self.getVocabulary()
+        ociproject = self.factory.makeOCIProject()
+        token = "%s/%s" % (ociproject.pillar.name, ociproject.name)
+        term = removeSecurityProxy(vocabulary).getTermByToken(token)
+        self.assertEqual(ociproject, term.value)
diff --git a/lib/lp/registry/vocabularies.py b/lib/lp/registry/vocabularies.py
index 05e3900..1cb109d 100644
--- a/lib/lp/registry/vocabularies.py
+++ b/lib/lp/registry/vocabularies.py
@@ -128,6 +128,7 @@ from lp.registry.interfaces.milestone import (
     IMilestoneSet,
     IProjectGroupMilestone,
     )
+from lp.registry.interfaces.ociproject import IOCIProjectSet
 from lp.registry.interfaces.person import (
     IPerson,
     IPersonSet,
@@ -155,6 +156,7 @@ from lp.registry.model.featuredproject import FeaturedProject
 from lp.registry.model.karma import KarmaCategory
 from lp.registry.model.mailinglist import MailingList
 from lp.registry.model.milestone import Milestone
+from lp.registry.model.ociproject import OCIProject
 from lp.registry.model.person import (
     get_person_visibility_terms,
     IrcID,
@@ -213,6 +215,7 @@ from lp.services.webapp.vocabulary import (
     NamedSQLObjectVocabulary,
     NamedStormHugeVocabulary,
     SQLObjectVocabularyBase,
+    StormVocabularyBase,
     VocabularyFilter,
     )
 from lp.soyuz.model.archive import Archive
@@ -2205,3 +2208,31 @@ class DistributionSourcePackageVocabulary(FilteredVocabularyBase):
             return self.toTerm((dsp, binary_names))
 
         return CountableIterator(results.count(), results, make_term)
+
+
+@implementer(IHugeVocabulary)
+class OCIProjectVocabulary(StormVocabularyBase):
+    """All OCI Projects."""
+
+    _table = OCIProject
+    displayname = 'Select an OCI project'
+    step_title = 'Search'
+
+    def toTerm(self, ociproject):
+        token = "%s/%s" % (ociproject.pillar.name, ociproject.name)
+        title = "%s" % token
+        return SimpleTerm(ociproject, token, title)
+
+    def getTermByToken(self, token):
+        pillar_name, name = token.split('/')
+        ociproject = getUtility(IOCIProjectSet).getByPillarAndName(
+            pillar_name, name)
+        if ociproject is None:
+            raise LookupError(token)
+        return self.toTerm(ociproject)
+
+    def search(self, query, vocab_filter=None):
+        return getUtility(IOCIProjectSet).findByName(query)
+
+    def _entries(self):
+        return getUtility(IOCIProjectSet).findByName('')
diff --git a/lib/lp/registry/vocabularies.zcml b/lib/lp/registry/vocabularies.zcml
index 4e2e488..37b7b14 100644
--- a/lib/lp/registry/vocabularies.zcml
+++ b/lib/lp/registry/vocabularies.zcml
@@ -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).
 -->
 
@@ -562,4 +562,15 @@
             permission="zope.Public"
             attributes="name title description"/>
     </class>
+
+    <securedutility
+        name="OCIProject"
+        component="lp.registry.vocabularies.OCIProjectVocabulary"
+        provides="zope.schema.interfaces.IVocabularyFactory">
+        <allow interface="zope.schema.interfaces.IVocabularyFactory" />
+    </securedutility>
+
+    <class class="lp.registry.vocabularies.OCIProjectVocabulary">
+        <allow interface="lp.services.webapp.vocabulary.IHugeVocabulary" />
+    </class>
 </configure>