4=================================================================== loops.search - Provide search functionality for the loops framework =================================================================== ($Id$) Let's do some basic set up >>> from zope.app.testing.setup import placefulSetUp, placefulTearDown >>> site = placefulSetUp(True) >>> from zope import component, interface >>> from zope.interface import implements and setup a simple loops site with a concept manager and some concepts (with all the type machinery, what in real life is done via standard ZCML setup): >>> from cybertools.relation.registry import DummyRelationRegistry >>> from cybertools.relation.tests import IntIdsStub >>> relations = DummyRelationRegistry() >>> component.provideUtility(relations) >>> component.provideUtility(IntIdsStub()) >>> from loops.type import ConceptType, TypeConcept >>> from loops.interfaces import ITypeConcept >>> component.provideAdapter(ConceptType) >>> component.provideAdapter(TypeConcept) >>> from loops import Loops >>> loopsRoot = site['loops'] = Loops() >>> from loops.setup import SetupManager >>> setup = SetupManager(loopsRoot) >>> concepts, resources, views = setup.setup() >>> typeConcept = concepts['type'] >>> from loops.concept import Concept >>> query = concepts['query'] = Concept(u'Query') >>> topic = concepts['topic'] = Concept(u'Topic') >>> for c in (query, topic): c.conceptType = typeConcept In addition we create a concept that holds the search page and a node (page) that links to this concept: >>> search = concepts['search'] = Concept(u'Search') >>> search.conceptType = query >>> from loops.view import Node >>> page = views['page'] = Node('Search Page') >>> page.target = search Search views ------------ Now we are ready to create a search view object: >>> from zope.publisher.browser import TestRequest >>> from loops.search.browser import Search >>> searchView = Search(search, TestRequest()) The search view provides values for identifying the search form itself and the parameter rows; the rowNum is auto-incremented, so it should be accessed exactly once per row: >>> searchView.rowNum 1 >>> searchView.rowNum 2 The search view provides vocabularies for types that allow the selection of types to search for; this needs an ITypeManager adapter registered via zcml in real life: >>> from loops.type import LoopsTypeManager >>> component.provideAdapter(LoopsTypeManager) >>> t = searchView.typesForSearch() >>> len(t) 11 >>> t.getTermByToken('loops:resource:*').title 'Any Resource' >>> t = searchView.conceptTypesForSearch() >>> len(t) 5 >>> t.getTermByToken('loops:concept:*').title 'Any Concept' To execute the search in the context of a node we have to set up a node view for our page. The submitReplacing method returns a JavaScript call that will replace the results part on the search page; as this registers the dojo library with the view's controller we also have to supply a controller attribute for the search view. >>> from loops.browser.node import NodeView >>> request = TestRequest() >>> pageView = NodeView(page, request) >>> from cybertools.browser.liquid.controller import Controller >>> searchView.controller = Controller(searchView, request) >>> searchView.submitReplacing('1.results', '1.search.form', pageView) 'return submitReplacing("1.results", "1.search.form", "http://127.0.0.1/loops/views/page/.target9/@@searchresults.html")' Basic (text/title) search ------------------------- The searchresults.html view, i.e. the SearchResults view class provides the result set of the search via its `results` property. Before accessing the `results` property we have to prepare a (for testing purposes fairly primitive) catalog and a resource we can search for: >>> from zope.app.catalog.interfaces import ICatalog >>> class DummyCat(object): ... implements(ICatalog) ... def searchResults(self, **criteria): ... name = criteria.get('loops_title') ... if name.endswith('*'): name = name[:-1] ... type = criteria.get('loops_type', ('resource',)) ... if name: ... if 'concept' in type[0]: ... result = concepts.get(name) ... else: ... result = resources.get(name) ... if result: ... return [result] ... return [] >>> component.provideUtility(DummyCat()) >>> from loops.resource import Resource >>> rplone = resources['plone'] = Resource() >>> from loops.search.browser import SearchResults >>> form = {'search.2.title': True, 'search.2.text': u'plone'} >>> request = TestRequest(form=form) >>> resultsView = SearchResults(page, request) >>> results = list(resultsView.results) >>> len(results) 1 >>> results[0].context == rplone True >>> form = {'search.2.title': True, 'search.2.text': u'foo'} >>> request = TestRequest(form=form) >>> resultsView = SearchResults(page, request) >>> len(list(resultsView.results)) 0 Search via related concepts --------------------------- We first have to prepare some test concepts (topics); we also assign our test resource (rplone) from above to one of the topics: >>> czope = concepts['zope'] = Concept(u'Zope') >>> czope2 = concepts['zope2'] = Concept(u'Zope 2') >>> czope3 = concepts['zope3'] = Concept(u'Zope 3') >>> cplone = concepts['plone'] = Concept(u'Plone') >>> for c in (czope, czope2, czope3, cplone): ... c.conceptType = topic >>> czope.assignChild(czope2) >>> czope.assignChild(czope3) >>> czope2.assignChild(cplone) >>> rplone.assignConcept(cplone) Now we can fill our search form and execute the query; note that all concepts found are listed, plus all their children and all resources associated with them: >>> form = {'search.3.type': 'loops:concept:topic', 'search.3.text': u'zope'} >>> request = TestRequest(form=form) >>> resultsView = SearchResults(page, request) >>> results = list(resultsView.results) >>> len(results) 5 >>> results[0].context.__name__ u'plone' >>> form = {'search.3.type': 'loops:concept:topic', 'search.3.text': u'zope3'} >>> request = TestRequest(form=form) >>> resultsView = SearchResults(page, request) >>> results = list(resultsView.results) >>> len(results) 1 >>> results[0].context.__name__ u'zope3' To support easy entry of concepts to search for we can preselect the available concepts (optionally restricted to a certain type) by entering text parts of the concepts' titles: >>> form = {'searchType': 'loops:concept:topic', 'searchString': u'zope'} >>> request = TestRequest(form=form) >>> view = Search(page, request) >>> view.listConcepts() "[['Zope (Topic)', '11']]" TODO - more to come...