diff --git a/README.txt b/README.txt index 1cd8060..b2df97d 100755 --- a/README.txt +++ b/README.txt @@ -121,6 +121,9 @@ type manager. >>> from loops.type import LoopsTypeManager, LoopsType >>> component.provideAdapter(LoopsTypeManager, (ILoopsObject,), ITypeManager) + >>> from loops.type import TypeConcept + >>> component.provideAdapter(TypeConcept) + >>> from loops.concept import ConceptTypeSourceList >>> types = ConceptTypeSourceList(cc1) >>> sorted(t.title for t in types) diff --git a/browser/common.py b/browser/common.py index 4e7da1c..6cc1acd 100644 --- a/browser/common.py +++ b/browser/common.py @@ -225,7 +225,7 @@ class BaseView(GenericView): return util.KeywordVocabulary(general + self.listTypesForSearch(('resource',), ('system', 'hidden'),)) - # controllling editing + # controlling editing @Lazy def editable(self): diff --git a/search/README.txt b/search/README.txt index 0dc1527..442f15f 100755 --- a/search/README.txt +++ b/search/README.txt @@ -1,4 +1,4 @@ -4=================================================================== +=================================================================== loops.search - Provide search functionality for the loops framework =================================================================== @@ -118,17 +118,21 @@ purposes fairly primitive) catalog and a resource we can search for: >>> class DummyCat(object): ... implements(ICatalog) ... def searchResults(self, **criteria): + ... result = [] ... 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 [] + ... if name and name.endswith('*'): name = name[:-1] + ... typeToken = criteria.get('loops_type', ('resource',)) + ... if name or typeToken: + ... if 'concept' in typeToken[0]: + ... if name: + ... result = concepts.get(name) + ... else: + ... tp = concepts[typeToken[0].split(':')[-1]] + ... result = list(tp.getChildren()) + ... else: + ... result = resources.get(name) + ... if not result: return [] + ... return type(result) is list and result or [result] >>> component.provideUtility(DummyCat()) >>> from loops.resource import Resource @@ -200,6 +204,59 @@ of the concepts' titles: >>> view.listConcepts() "[['Zope (Topic)', '11']]" +Preset Concept Types on Search Forms +------------------------------------ + +Often we want to include certain types in our search. We can instruct +the search form to include lines for these types by giving these types +a certain qualifier, via the option attribute of the type interface. + +Let's start with a new type, the customer type. + + >>> customer = concepts['customer'] = Concept('Customer') + >>> customer.conceptType = typeConcept + >>> custType = ITypeConcept(customer) + >>> custType.options + [] + + >>> cust1 = concepts['cust1'] = Concept(u'Zope Corporation') + >>> cust2 = concepts['cust2'] = Concept(u'cyberconcepts') + >>> for c in (cust1, cust2): c.conceptType = customer + + >>> from cybertools.typology.interfaces import IType + >>> IType(cust1).qualifiers + ('concept',) + + >>> searchView = Search(search, TestRequest()) + >>> list(searchView.presetSearchTypes) + [] + +We can now add a 'search' qualifier to the customer type's options +and thus include the customer type in the preset search types. + + >>> custType.options = ('qualifier:search',) + >>> IType(cust1).qualifiers + ('concept', 'search') + >>> searchView = Search(search, TestRequest()) + >>> list(searchView.presetSearchTypes) + [{'token': 'loops:concept:customer', 'title': 'Customer'}] + + >>> searchView.conceptsForType('loops:concept:customer') + [{'token': 'none', 'title': u'not selected'}, + {'token': '17', 'title': u'Zope Corporation'}, + {'token': '18', 'title': u'cyberconcepts'}] + +Let's use this new search option for querying: + + >>> form = {'search.4.text_selected': u'17'} + >>> resultsView = SearchResults(page, TestRequest(form=form)) + >>> results = list(resultsView.results) + >>> results[0].title + u'Zope Corporation' + + +Automatic Filtering +------------------- + TODO - more to come... - diff --git a/search/browser.py b/search/browser.py index 7093edd..6c15bab 100644 --- a/search/browser.py +++ b/search/browser.py @@ -64,6 +64,20 @@ class Search(BaseView): self.maxRowNum = n return n + @Lazy + def presetSearchTypes(self): + """ Return a list of concept type info dictionaries (see BaseView) + that should be displayed on a separate search parameter row. + """ + #return ITypeManager(self.context).listTypes(include=('search',)) + return self.listTypesForSearch(include=('search',)) + + def conceptsForType(self, token): + noSelection = dict(token='none', title=u'not selected') + result = sorted(ConceptQuery(self).query(type=token), key=lambda x: x.title) + return [noSelection] + [dict(title=o.title, token=util.getUidForObject(o)) + for o in result] + def initDojo(self): self.registerDojo() cm = self.controller.macros @@ -106,18 +120,33 @@ class SearchResults(BaseView): @Lazy def results(self): - request = self.request - type = request.get('search.1.text', 'loops:*') - text = request.get('search.2.text') - useTitle = request.get('search.2.title') - useFull = request.get('search.2.full') - conceptType = request.get('search.3.type', 'loops:concept:*') - conceptTitle = request.get('search.3.text') - conceptUid = request.get('search.3.text_selected') + form = self.request.form + type = form.get('search.1.text', 'loops:*') + text = form.get('search.2.text') + useTitle = form.get('search.2.title') + useFull = form.get('search.2.full') + conceptType = form.get('search.3.type', 'loops:concept:*') + conceptTitle = form.get('search.3.text') + conceptUid = form.get('search.3.text_selected') result = FullQuery(self).query(text=text, type=type, useTitle=useTitle, useFull=useFull, conceptTitle=conceptTitle, conceptUid=conceptUid, - conceptType= conceptType) + conceptType=conceptType) + rowNum = 4 + while rowNum < 10: + addCriteria = form.get('search.%i.text_selected' % rowNum) + rowNum += 1 + if not addCriteria: + break + if addCriteria == 'none': + continue + addSelection = FullQuery(self).query(text=text, type=type, + useTitle=useTitle, useFull=useFull, + conceptUid=addCriteria) + if result: + result = [r for r in result if r in addSelection] + else: + result = addSelection result = sorted(result, key=lambda x: x.title.lower()) return self.viewIterator(result) diff --git a/search/search.pt b/search/search.pt index 0adb318..64a1223 100644 --- a/search/search.pt +++ b/search/search.pt @@ -84,6 +84,7 @@
-

Search via related concepts

+

Search via related concepts

+ + + + + Type: + + + + + + + + + + + diff --git a/type.py b/type.py index c54b200..ba2f76f 100644 --- a/type.py +++ b/type.py @@ -85,6 +85,9 @@ class LoopsType(BaseType): # check typeProvider for additional qualifiers: if self.typeProvider in (self.typeConcept, self.predicateType,): qu.append('system') + addQualifiers = self.optionsDict.get('qualifier') + if addQualifiers: + qu.extend(addQualifiers.split(',')) # how to set a type to 'hidden'? return tuple(qu) diff --git a/util.py b/util.py index 07c6f86..82c46c2 100644 --- a/util.py +++ b/util.py @@ -64,6 +64,13 @@ def nl2br(text): else: # gracefully handle Mac line endings return '
\n'.join(text.split('\r')) +def toUnicode(text): + if type(text) is not unicode: + return text.decode('UTF-8') + else: + return text + + def getObjectForUid(uid): if uid == '*': # wild card return '*' @@ -76,9 +83,3 @@ def getUidForObject(obj): intIds = component.getUtility(IIntIds) return str(intIds.queryId(obj)) - -def toUnicode(text): - if type(text) is not unicode: - return text.decode('UTF-8') - else: - return text