← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~pappacena/launchpad:ui-ociproject-search-on-projects into launchpad:master

 

Thiago F. Pappacena has proposed merging ~pappacena/launchpad:ui-ociproject-search-on-projects into launchpad:master with ~pappacena/launchpad:gitrepo-ociproject-refactoring as a prerequisite.

Commit message:
OCIProject search on project page.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~pappacena/launchpad/+git/launchpad/+merge/384514
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of ~pappacena/launchpad:ui-ociproject-search-on-projects into launchpad:master.
diff --git a/lib/lp/registry/browser/configure.zcml b/lib/lp/registry/browser/configure.zcml
index b8dc1bb..a409eb1 100644
--- a/lib/lp/registry/browser/configure.zcml
+++ b/lib/lp/registry/browser/configure.zcml
@@ -2158,9 +2158,16 @@
     <browser:page
         name="+search-oci-project"
         for="lp.registry.interfaces.distribution.IDistribution"
-        class="lp.registry.browser.distribution.DistributionOCIProjectSearchView"
+        class="lp.registry.browser.ociproject.OCIProjectSearchView"
         permission="zope.Public"
-        template="../templates/distribution-search-oci-projects.pt"
+        template="../templates/search-oci-projects.pt"
+        />
+    <browser:page
+        name="+search-oci-project"
+        for="lp.registry.interfaces.product.IProduct"
+        class="lp.registry.browser.ociproject.OCIProjectSearchView"
+        permission="zope.Public"
+        template="../templates/search-oci-projects.pt"
         />
     <browser:page
         name="+search"
diff --git a/lib/lp/registry/browser/distribution.py b/lib/lp/registry/browser/distribution.py
index ffabedb..0585def 100644
--- a/lib/lp/registry/browser/distribution.py
+++ b/lib/lp/registry/browser/distribution.py
@@ -42,7 +42,6 @@ from collections import defaultdict
 import datetime
 
 from lazr.restful.utils import smartquote
-import six
 from zope.component import getUtility
 from zope.event import notify
 from zope.formlib import form
@@ -106,7 +105,6 @@ from lp.registry.interfaces.distributionmirror import (
     MirrorContent,
     MirrorSpeed,
     )
-from lp.registry.interfaces.ociproject import IOCIProjectSet
 from lp.registry.interfaces.series import SeriesStatus
 from lp.services.database.decoratedresultset import DecoratedResultSet
 from lp.services.feeds.browser import FeedsMixin
@@ -1376,50 +1374,3 @@ class DistributionPublisherConfigView(LaunchpadFormView):
         self.request.response.addInfoNotification(
             'Your changes have been applied.')
         self.next_url = canonical_url(self.context)
-
-
-class DistributionOCIProjectSearchView(LaunchpadView):
-    """Page to search for OCI projects of a given distribution."""
-    page_title = ''
-
-    @property
-    def label(self):
-        return "Search OCI projects in %s" % self.context.title
-
-    @property
-    def text(self):
-        text = self.request.get("text", None)
-        if isinstance(text, list):
-            # The user may have URL hacked a query string with more than one
-            # "text" parameter. We'll take the last one.
-            text = text[-1]
-        return text
-
-    @property
-    def search_requested(self):
-        return self.text is not None
-
-    @property
-    def title(self):
-        return self.context.name
-
-    @cachedproperty
-    def count(self):
-        """Return the number of matched search results."""
-        return self.batchnav.batch.total()
-
-    @cachedproperty
-    def batchnav(self):
-        """Return the batch navigator for the search results."""
-        return BatchNavigator(self.search_results, self.request)
-
-    @cachedproperty
-    def preloaded_batch(self):
-        projects = self.batchnav.batch
-        getUtility(IOCIProjectSet).preloadDataForOCIProjects(projects)
-        return projects
-
-    @property
-    def search_results(self):
-        return getUtility(IOCIProjectSet).findByDistributionAndName(
-            self.context, self.text or six.ensure_text(''))
diff --git a/lib/lp/registry/browser/ociproject.py b/lib/lp/registry/browser/ociproject.py
index df7ac85..c4b8fca 100644
--- a/lib/lp/registry/browser/ociproject.py
+++ b/lib/lp/registry/browser/ociproject.py
@@ -14,6 +14,7 @@ __all__ = [
     'OCIProjectNavigationMenu',
     ]
 
+import six
 from zope.component import getUtility
 from zope.formlib import form
 from zope.interface import implementer
@@ -39,16 +40,19 @@ from lp.registry.interfaces.ociprojectname import (
     IOCIProjectNameSet,
     )
 from lp.services.features import getFeatureFlag
+from lp.services.propertycache import cachedproperty
 from lp.services.webapp import (
     canonical_url,
     ContextMenu,
     enabled_with_permission,
+    LaunchpadView,
     Link,
     Navigation,
     NavigationMenu,
     StandardLaunchpadFacets,
     stepthrough,
     )
+from lp.services.webapp.batching import BatchNavigator
 from lp.services.webapp.breadcrumb import Breadcrumb
 from lp.services.webapp.interfaces import IMultiFacetedBreadcrumb
 
@@ -213,3 +217,50 @@ class OCIProjectEditView(LaunchpadEditFormView):
         return canonical_url(self.context)
 
     cancel_url = next_url
+
+
+class OCIProjectSearchView(LaunchpadView):
+    """Page to search for OCI projects of a given pillar."""
+    page_title = ''
+
+    @property
+    def label(self):
+        return "Search OCI projects in %s" % self.context.title
+
+    @property
+    def text(self):
+        text = self.request.get("text", None)
+        if isinstance(text, list):
+            # The user may have URL hacked a query string with more than one
+            # "text" parameter. We'll take the last one.
+            text = text[-1]
+        return text
+
+    @property
+    def search_requested(self):
+        return self.text is not None
+
+    @property
+    def title(self):
+        return self.context.name
+
+    @cachedproperty
+    def count(self):
+        """Return the number of matched search results."""
+        return self.batchnav.batch.total()
+
+    @cachedproperty
+    def batchnav(self):
+        """Return the batch navigator for the search results."""
+        return BatchNavigator(self.search_results, self.request)
+
+    @cachedproperty
+    def preloaded_batch(self):
+        projects = self.batchnav.batch
+        getUtility(IOCIProjectSet).preloadDataForOCIProjects(projects)
+        return projects
+
+    @property
+    def search_results(self):
+        return getUtility(IOCIProjectSet).findByPillarAndName(
+            self.context, self.text or six.ensure_text(''))
diff --git a/lib/lp/registry/browser/tests/test_distribution.py b/lib/lp/registry/browser/tests/test_distribution.py
index 0979ec2..b271bfe 100644
--- a/lib/lp/registry/browser/tests/test_distribution.py
+++ b/lib/lp/registry/browser/tests/test_distribution.py
@@ -24,18 +24,11 @@ from lp.registry.interfaces.series import SeriesStatus
 from lp.services.webapp import canonical_url
 from lp.testing import (
     admin_logged_in,
-    BrowserTestCase,
     login_celebrity,
     login_person,
-    record_two_runs,
     TestCaseWithFactory,
     )
 from lp.testing.layers import DatabaseFunctionalLayer
-from lp.testing.pages import (
-    extract_text,
-    find_tag_by_id,
-    find_tags_by_class,
-    )
 from lp.testing.views import create_initialized_view
 
 
@@ -163,133 +156,3 @@ class TestDistributionView(TestCaseWithFactory):
         self.assertContentEqual(
             team_membership_policy_data,
             cache.objects['team_membership_policy_data'])
-
-
-class TestDistributionOCIProjectSearchView(BrowserTestCase):
-
-    layer = DatabaseFunctionalLayer
-
-    def assertPaginationIsPresent(
-            self, browser, results_in_page, total_result):
-        """Checks that pagination is shown at the browser."""
-        nav_index = find_tags_by_class(
-            browser.contents, "batch-navigation-index")[0]
-        nav_index_text = extract_text(nav_index).replace('\n', ' ')
-        self.assertIn(
-            "1 → %s of %s results" % (results_in_page, total_result),
-            nav_index_text)
-
-        nav_links = find_tags_by_class(
-            browser.contents, "batch-navigation-links")[0]
-        nav_links_text = extract_text(nav_links).replace('\n', ' ')
-        self.assertIn("First • Previous • Next • Last", nav_links_text)
-
-    def assertOCIProjectsArePresent(self, browser, oci_projects):
-        table = find_tag_by_id(browser.contents, "projects_list")
-        with admin_logged_in():
-            for oci_project in oci_projects:
-                url = canonical_url(oci_project, force_local_path=True)
-                self.assertIn(url, str(table))
-                self.assertIn(oci_project.name, str(table))
-
-    def assertOCIProjectsAreNotPresent(self, browser, oci_projects):
-        table = find_tag_by_id(browser.contents, "projects_list")
-        with admin_logged_in():
-            for oci_project in oci_projects:
-                url = canonical_url(oci_project, force_local_path=True)
-                self.assertNotIn(url, str(table))
-                self.assertNotIn(oci_project.name, str(table))
-
-    def test_search_no_oci_projects(self):
-        person = self.factory.makePerson()
-        distribution = self.factory.makeDistribution()
-        browser = self.getViewBrowser(
-            distribution, user=person, view_name='+search-oci-project')
-
-        main_portlet = find_tags_by_class(browser.contents, "main-portlet")[0]
-        self.assertIn(
-            "There are no OCI projects registered for %s" % distribution.name,
-            extract_text(main_portlet).replace("\n", " "))
-
-    def test_oci_projects_no_search_keyword(self):
-        person = self.factory.makePerson()
-        distro = self.factory.makeDistribution(owner=person)
-
-        # Creates 3 OCI Projects
-        oci_projects = [
-            self.factory.makeOCIProject(
-                ociprojectname="test-project-%s" % i,
-                registrant=person, pillar=distro) for i in range(3)]
-
-        browser = self.getViewBrowser(
-            distro, user=person, view_name='+search-oci-project')
-
-        # Check top message.
-        main_portlet = find_tags_by_class(browser.contents, "main-portlet")[0]
-        self.assertIn(
-            "There are 3 OCI projects registered for %s" % distro.name,
-            extract_text(main_portlet).replace("\n", " "))
-
-        self.assertOCIProjectsArePresent(browser, oci_projects)
-        self.assertPaginationIsPresent(browser, 3, 3)
-
-    def test_oci_projects_with_search_keyword(self):
-        person = self.factory.makePerson()
-        distro = self.factory.makeDistribution(owner=person)
-
-        # And 2 OCI projects that will match the name
-        oci_projects = [
-            self.factory.makeOCIProject(
-                ociprojectname="find-me-%s" % i,
-                registrant=person, pillar=distro) for i in range(2)]
-
-        # Creates 2 OCI Projects that will not match search
-        other_oci_projects = [
-            self.factory.makeOCIProject(
-                ociprojectname="something-%s" % i,
-                registrant=person, pillar=distro) for i in range(2)]
-
-        browser = self.getViewBrowser(
-            distro, user=person, view_name='+search-oci-project')
-        browser.getControl(name="text").value = "find-me"
-        browser.getControl("Search").click()
-
-        # Check top message.
-        main_portlet = find_tags_by_class(browser.contents, "main-portlet")[0]
-        self.assertIn(
-            'There are 2 OCI projects registered for %s matching "%s"' %
-            (distro.name, "find-me"),
-            extract_text(main_portlet).replace("\n", " "))
-
-        self.assertOCIProjectsArePresent(browser, oci_projects)
-        self.assertOCIProjectsAreNotPresent(browser, other_oci_projects)
-        self.assertPaginationIsPresent(browser, 2, 2)
-
-    def test_query_count_is_constant(self):
-        batch_size = 3
-        self.pushConfig("launchpad", default_batch_size=batch_size)
-
-        person = self.factory.makePerson()
-        distro = self.factory.makeDistribution(owner=person)
-        name_pattern = "find-me-"
-
-        def createOCIProject():
-            self.factory.makeOCIProject(
-                ociprojectname=self.factory.getUniqueString(name_pattern),
-                pillar=distro)
-
-        viewer = self.factory.makePerson()
-
-        def getView():
-            browser = self.getViewBrowser(
-                distro, user=viewer, view_name='+search-oci-project')
-            browser.getControl(name="text").value = name_pattern
-            browser.getControl("Search").click()
-            return browser
-
-        def do_login():
-            login_person(person)
-
-        recorder1, recorder2 = record_two_runs(
-            getView, createOCIProject, 1, 10, login_method=do_login)
-        self.assertEqual(recorder1.count, recorder2.count)
diff --git a/lib/lp/registry/browser/tests/test_ociproject.py b/lib/lp/registry/browser/tests/test_ociproject.py
index 5187cd6..70cfd71 100644
--- a/lib/lp/registry/browser/tests/test_ociproject.py
+++ b/lib/lp/registry/browser/tests/test_ociproject.py
@@ -12,6 +12,7 @@ __all__ = []
 from datetime import datetime
 
 import pytz
+from zope.security.proxy import removeSecurityProxy
 
 from lp.oci.interfaces.ocirecipe import OCI_RECIPE_ALLOW_CREATE
 from lp.registry.interfaces.ociproject import (
@@ -25,7 +26,9 @@ from lp.services.webapp.escaping import structured
 from lp.testing import (
     admin_logged_in,
     BrowserTestCase,
+    login_person,
     person_logged_in,
+    record_two_runs,
     test_tales,
     TestCaseWithFactory,
     )
@@ -34,6 +37,7 @@ from lp.testing.matchers import MatchesTagText
 from lp.testing.pages import (
     extract_text,
     find_main_content,
+    find_tag_by_id,
     find_tags_by_class,
     )
 from lp.testing.publication import test_traverse
@@ -312,3 +316,162 @@ class TestOCIProjectAddView(BrowserTestCase):
             new_distribution,
             user=another_person,
             view_name='+new-oci-project')
+
+
+class TestOCIProjectSearchView(BrowserTestCase):
+
+    layer = DatabaseFunctionalLayer
+
+    def assertPaginationIsPresent(
+            self, browser, results_in_page, total_result):
+        """Checks that pagination is shown at the browser."""
+        nav_index = find_tags_by_class(
+            browser.contents, "batch-navigation-index")[0]
+        nav_index_text = extract_text(nav_index).replace('\n', ' ')
+        self.assertIn(
+            "1 → %s of %s results" % (results_in_page, total_result),
+            nav_index_text)
+
+        nav_links = find_tags_by_class(
+            browser.contents, "batch-navigation-links")[0]
+        nav_links_text = extract_text(nav_links).replace('\n', ' ')
+        self.assertIn("First • Previous • Next • Last", nav_links_text)
+
+    def assertOCIProjectsArePresent(self, browser, oci_projects):
+        table = find_tag_by_id(browser.contents, "projects_list")
+        with admin_logged_in():
+            for oci_project in oci_projects:
+                url = canonical_url(oci_project, force_local_path=True)
+                self.assertIn(url, str(table))
+                self.assertIn(oci_project.name, str(table))
+
+    def assertOCIProjectsAreNotPresent(self, browser, oci_projects):
+        table = find_tag_by_id(browser.contents, "projects_list")
+        with admin_logged_in():
+            for oci_project in oci_projects:
+                url = canonical_url(oci_project, force_local_path=True)
+                self.assertNotIn(url, str(table))
+                self.assertNotIn(oci_project.name, str(table))
+
+    def check_search_no_oci_projects(self, pillar):
+        pillar = removeSecurityProxy(pillar)
+        person = self.factory.makePerson()
+
+        browser = self.getViewBrowser(
+            pillar, user=person, view_name='+search-oci-project')
+
+        main_portlet = find_tags_by_class(browser.contents, "main-portlet")[0]
+        self.assertIn(
+            "There are no OCI projects registered for %s" % pillar.name,
+            extract_text(main_portlet).replace("\n", " "))
+
+    def test_search_no_oci_projects_distribution_pillar(self):
+        return self.check_search_no_oci_projects(
+            self.factory.makeDistribution())
+
+    def test_search_no_oci_projects_project_pillar(self):
+        return self.check_search_no_oci_projects(self.factory.makeProduct())
+
+    def check_oci_projects_no_search_keyword(self, pillar):
+        pillar = removeSecurityProxy(pillar)
+        person = pillar.owner
+
+        # Creates 3 OCI Projects
+        oci_projects = [
+            self.factory.makeOCIProject(
+                ociprojectname="test-project-%s" % i,
+                registrant=person, pillar=pillar) for i in range(3)]
+
+        browser = self.getViewBrowser(
+            pillar, user=person, view_name='+search-oci-project')
+
+        # Check top message.
+        main_portlet = find_tags_by_class(browser.contents, "main-portlet")[0]
+        self.assertIn(
+            "There are 3 OCI projects registered for %s" % pillar.name,
+            extract_text(main_portlet).replace("\n", " "))
+
+        self.assertOCIProjectsArePresent(browser, oci_projects)
+        self.assertPaginationIsPresent(browser, 3, 3)
+
+    def test_oci_projects_no_search_keyword_for_distribution(self):
+        return self.check_oci_projects_no_search_keyword(
+            self.factory.makeDistribution())
+
+    def test_oci_projects_no_search_keyword_for_project(self):
+        return self.check_oci_projects_no_search_keyword(
+            self.factory.makeProduct())
+
+    def check_oci_projects_with_search_keyword(self, pillar):
+        pillar = removeSecurityProxy(pillar)
+        person = pillar.owner
+
+        # And 2 OCI projects that will match the name
+        oci_projects = [
+            self.factory.makeOCIProject(
+                ociprojectname="find-me-%s" % i,
+                registrant=person, pillar=pillar) for i in range(2)]
+
+        # Creates 2 OCI Projects that will not match search
+        other_oci_projects = [
+            self.factory.makeOCIProject(
+                ociprojectname="something-%s" % i,
+                registrant=person, pillar=pillar) for i in range(2)]
+
+        browser = self.getViewBrowser(
+            pillar, user=person, view_name='+search-oci-project')
+        browser.getControl(name="text").value = "find-me"
+        browser.getControl("Search").click()
+
+        # Check top message.
+        main_portlet = find_tags_by_class(browser.contents, "main-portlet")[0]
+        self.assertIn(
+            'There are 2 OCI projects registered for %s matching "%s"' %
+            (pillar.name, "find-me"),
+            extract_text(main_portlet).replace("\n", " "))
+
+        self.assertOCIProjectsArePresent(browser, oci_projects)
+        self.assertOCIProjectsAreNotPresent(browser, other_oci_projects)
+        self.assertPaginationIsPresent(browser, 2, 2)
+
+    def test_oci_projects_with_search_keyword_for_distribution(self):
+        self.check_oci_projects_with_search_keyword(
+            self.factory.makeDistribution())
+
+    def test_oci_projects_with_search_keyword_for_project(self):
+        self.check_oci_projects_with_search_keyword(self.factory.makeProduct())
+
+    def check_query_count_is_constant(self, pillar):
+        batch_size = 3
+        self.pushConfig("launchpad", default_batch_size=batch_size)
+
+        person = self.factory.makePerson()
+        distro = self.factory.makeDistribution(owner=person)
+        name_pattern = "find-me-"
+
+        def createOCIProject():
+            self.factory.makeOCIProject(
+                ociprojectname=self.factory.getUniqueString(name_pattern),
+                pillar=distro)
+
+        viewer = self.factory.makePerson()
+
+        def getView():
+            browser = self.getViewBrowser(
+                distro, user=viewer, view_name='+search-oci-project')
+            browser.getControl(name="text").value = name_pattern
+            browser.getControl("Search").click()
+            return browser
+
+        def do_login():
+            login_person(person)
+
+        recorder1, recorder2 = record_two_runs(
+            getView, createOCIProject, 1, 10, login_method=do_login)
+        self.assertEqual(recorder1.count, recorder2.count)
+
+    def test_query_count_is_constant_for_distribution(self):
+        self.check_query_count_is_constant(self.factory.makeDistribution())
+
+    def test_query_count_is_constant_for_project(self):
+        self.check_query_count_is_constant(self.factory.makeProduct())
diff --git a/lib/lp/registry/interfaces/ociproject.py b/lib/lp/registry/interfaces/ociproject.py
index d6f8468..a99850d 100644
--- a/lib/lp/registry/interfaces/ociproject.py
+++ b/lib/lp/registry/interfaces/ociproject.py
@@ -203,9 +203,9 @@ class IOCIProjectSet(Interface):
         :return: The OCIProject found.
         """
 
-    def findByDistributionAndName(distribution, name_substring):
-        """Find OCIProjects for a given distribution that contains the
-        provided name."""
+    def findByPillarAndName(pillar, name_substring):
+        """Find OCIProjects for a given pillar that contains 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 387d9fe..2b72a98 100644
--- a/lib/lp/registry/model/ociproject.py
+++ b/lib/lp/registry/model/ociproject.py
@@ -32,8 +32,8 @@ from lp.registry.interfaces.ociproject import (
     IOCIProjectSet,
     )
 from lp.registry.interfaces.ociprojectname import IOCIProjectNameSet
-from lp.registry.interfaces.product import IProduct
 from lp.registry.interfaces.person import IPersonSet
+from lp.registry.interfaces.product import IProduct
 from lp.registry.interfaces.series import SeriesStatus
 from lp.registry.model.ociprojectname import OCIProjectName
 from lp.registry.model.ociprojectseries import OCIProjectSeries
@@ -275,11 +275,11 @@ class OCIProjectSet:
             OCIProjectName.name == name).one()
         return target
 
-    def findByDistributionAndName(self, distribution, name_substring):
+    def findByPillarAndName(self, pillar, name_substring):
         """See `IOCIProjectSet`."""
         return IStore(OCIProject).find(
             OCIProject,
-            OCIProject.distribution == distribution,
+            self._get_pillar_attribute(pillar) == pillar,
             OCIProject.ociprojectname == OCIProjectName.id,
             OCIProjectName.name.contains_string(name_substring))
 
diff --git a/lib/lp/registry/templates/distribution-search-oci-projects.pt b/lib/lp/registry/templates/search-oci-projects.pt
similarity index 100%
rename from lib/lp/registry/templates/distribution-search-oci-projects.pt
rename to lib/lp/registry/templates/search-oci-projects.pt

References