work in progress: search
git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@1294 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
parent
b4131bfcaf
commit
3bd8e181be
10 changed files with 231 additions and 44 deletions
|
@ -40,6 +40,7 @@ from zope.schema.vocabulary import SimpleTerm
|
||||||
from zope.security import canAccess, canWrite
|
from zope.security import canAccess, canWrite
|
||||||
from zope.security.proxy import removeSecurityProxy
|
from zope.security.proxy import removeSecurityProxy
|
||||||
|
|
||||||
|
from cybertools.relation.interfaces import IRelationRegistry
|
||||||
from cybertools.typology.interfaces import IType
|
from cybertools.typology.interfaces import IType
|
||||||
from loops.interfaces import IView
|
from loops.interfaces import IView
|
||||||
from loops import util
|
from loops import util
|
||||||
|
@ -167,7 +168,8 @@ class BaseView(object):
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def uniqueId(self):
|
def uniqueId(self):
|
||||||
return zapi.getUtility(IIntIds).getId(self.context)
|
return zapi.getUtility(IRelationRegistry).getUniqueIdForObject(self.context)
|
||||||
|
#return zapi.getUtility(IIntIds).getId(self.context)
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def editable(self):
|
def editable(self):
|
||||||
|
|
|
@ -74,3 +74,13 @@ div.image {
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* search stuff */
|
||||||
|
|
||||||
|
.searchForm input.button, input.submit {
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.searchForm input.submit {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,9 +5,19 @@ function openEditWindow(url) {
|
||||||
zmi.focus();
|
zmi.focus();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function focusOpener() {
|
function focusOpener() {
|
||||||
if (typeof(opener) != 'undefined' && opener != null) {
|
if (typeof(opener) != 'undefined' && opener != null) {
|
||||||
opener.location.reload();
|
opener.location.reload();
|
||||||
opener.focus();
|
opener.focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function submitReplacing(targetId, formId, actionUrl) {
|
||||||
|
dojo.io.updateNode(targetId, {
|
||||||
|
url: actionUrl,
|
||||||
|
formNode: dojo.byId(formId),
|
||||||
|
method: 'post'
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
|
@ -8,7 +8,9 @@
|
||||||
tal:define="dummy python:
|
tal:define="dummy python:
|
||||||
controller.macros.register('css', resourceName='loops.css', media='all');
|
controller.macros.register('css', resourceName='loops.css', media='all');
|
||||||
dummy python:
|
dummy python:
|
||||||
controller.macros.register('js', resourceName='loops.js');" />
|
controller.macros.register('js', resourceName='loops.js');
|
||||||
|
dummy python:
|
||||||
|
controller.macros.register('js', resourceName='ajax.dojo/dojo.js');" />
|
||||||
|
|
||||||
|
|
||||||
<metal:block fill-slot="actions" />
|
<metal:block fill-slot="actions" />
|
||||||
|
|
|
@ -122,6 +122,13 @@ class NodeView(BaseView):
|
||||||
basicView._viewName = self.context.viewName
|
basicView._viewName = self.context.viewName
|
||||||
return basicView.view
|
return basicView.view
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def targetUrl(self):
|
||||||
|
t = self.target
|
||||||
|
if t is not None:
|
||||||
|
return '%s/.target%s' % (self.url, t.uniqueId)
|
||||||
|
return ''
|
||||||
|
|
||||||
def renderTarget(self):
|
def renderTarget(self):
|
||||||
target = self.target
|
target = self.target
|
||||||
return target is not None and target.render() or u''
|
return target is not None and target.render() or u''
|
||||||
|
|
|
@ -291,8 +291,6 @@
|
||||||
<adapter factory="loops.type.ConceptType" />
|
<adapter factory="loops.type.ConceptType" />
|
||||||
<adapter factory="loops.type.ResourceType"
|
<adapter factory="loops.type.ResourceType"
|
||||||
for="loops.interfaces.IDocument" />
|
for="loops.interfaces.IDocument" />
|
||||||
<!--<adapter factory="loops.type.ResourceType"
|
|
||||||
for="loops.interfaces.IMediaAsset" />-->
|
|
||||||
<adapter factory="loops.type.LoopsTypeManager" />
|
<adapter factory="loops.type.LoopsTypeManager" />
|
||||||
|
|
||||||
<adapter factory="loops.type.TypeConcept" trusted="True" />
|
<adapter factory="loops.type.TypeConcept" trusted="True" />
|
||||||
|
|
|
@ -4,3 +4,76 @@ loops.search - Provide search functionality for the loops framework
|
||||||
|
|
||||||
($Id$)
|
($Id$)
|
||||||
|
|
||||||
|
|
||||||
|
Let's do some basic set up
|
||||||
|
|
||||||
|
>>> from zope.app.testing.setup import placefulSetUp, placefulTearDown
|
||||||
|
>>> site = placefulSetUp(True)
|
||||||
|
|
||||||
|
>>> from zope import component, interface
|
||||||
|
|
||||||
|
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.interfaces import IRelationRegistry
|
||||||
|
>>> from cybertools.relation.registry import DummyRelationRegistry
|
||||||
|
>>> relations = DummyRelationRegistry()
|
||||||
|
>>> component.provideUtility(relations, IRelationRegistry)
|
||||||
|
|
||||||
|
>>> 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
|
||||||
|
|
||||||
|
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.itemNum
|
||||||
|
1
|
||||||
|
>>> searchView.rowNum
|
||||||
|
1
|
||||||
|
>>> searchView.rowNum
|
||||||
|
2
|
||||||
|
|
||||||
|
To execute the search in the context of a node we have to set up a node
|
||||||
|
view on our page. The submitReplacing method returns a JavaScript call
|
||||||
|
that will replace the results part on the search page:
|
||||||
|
|
||||||
|
>>> from loops.browser.node import NodeView
|
||||||
|
>>> pageView = NodeView(page, TestRequest())
|
||||||
|
|
||||||
|
>>> searchView.submitReplacing('1.results', '1.search.form', pageView)
|
||||||
|
'return submitReplacing("1.results", "1.search.form",
|
||||||
|
"http://127.0.0.1/loops/views/page/.target19/@@searchresults.html")'
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,42 @@ class Search(BaseView):
|
||||||
|
|
||||||
template = NamedTemplate('loops.search_macros')
|
template = NamedTemplate('loops.search_macros')
|
||||||
|
|
||||||
|
maxRowNum = 0
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def macro(self):
|
def macro(self):
|
||||||
return self.template.macros['search']
|
return self.template.macros['search']
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def itemNum(self):
|
||||||
|
""" Return a number identifying the item (the current search form)
|
||||||
|
on the page.
|
||||||
|
"""
|
||||||
|
return self.request.get('loops.itemNum', 1)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def rowNum(self):
|
||||||
|
""" Return the rowNum to be used for identifying the current row.
|
||||||
|
"""
|
||||||
|
n = self.request.get('loops.rowNum', 0)
|
||||||
|
if n: # if given directly we don't use the calculation
|
||||||
|
return n
|
||||||
|
n = (self.maxRowNum or self.request.get('loops.maxRowNum', 0)) + 1
|
||||||
|
self.maxRowNum = n
|
||||||
|
return n
|
||||||
|
|
||||||
|
def submitReplacing(self, targetId, formId, view):
|
||||||
|
return 'return submitReplacing("%s", "%s", "%s")' % (
|
||||||
|
targetId, formId,
|
||||||
|
'%s/.target%s/@@searchresults.html' % (view.url, self.uniqueId))
|
||||||
|
|
||||||
|
|
||||||
|
class SearchResults(BaseView):
|
||||||
|
|
||||||
|
innerHtml_template = NamedTemplate('loops.search_macros')
|
||||||
|
innerHtml_macro = 'search_results'
|
||||||
|
template = NamedTemplate('ajax.inner.html')
|
||||||
|
|
||||||
|
def __call__(self):
|
||||||
|
return self.template(self)
|
||||||
|
|
||||||
|
|
|
@ -24,5 +24,11 @@
|
||||||
permission="zope.View"
|
permission="zope.View"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<browser:page
|
||||||
|
name="searchresults.html"
|
||||||
|
for="loops.interfaces.ILoopsObject"
|
||||||
|
class="loops.search.browser.SearchResults"
|
||||||
|
permission="zope.View"
|
||||||
|
/>
|
||||||
|
|
||||||
</configure>
|
</configure>
|
||||||
|
|
124
search/search.pt
124
search/search.pt
|
@ -1,24 +1,33 @@
|
||||||
<metal:search define-macro="search">
|
<metal:search define-macro="search">
|
||||||
<div id="search"
|
<div id="search"
|
||||||
tal:define="template nocall:item/template">
|
tal:define="macros item/template/macros">
|
||||||
<h3 tal:attributes="class string:content-$level;
|
<h3 tal:attributes="class string:content-$level;
|
||||||
ondblclick item/openEditWindow">
|
ondblclick item/openEditWindow">
|
||||||
Search
|
Search
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div metal:define-macro="search_form">
|
<div metal:define-macro="search_form" class="searchForm"
|
||||||
|
tal:define="idPrefix string:${item/itemNum}.search;
|
||||||
|
formId string:$idPrefix.form">
|
||||||
<fieldset class="box">
|
<fieldset class="box">
|
||||||
<form action=".">
|
<form action="." method="post" id="1.search.form"
|
||||||
|
tal:attributes="id formId">
|
||||||
<table cellpadding="3">
|
<table cellpadding="3">
|
||||||
<tal:block define="search_selection string:type">
|
<tal:block repeat="search_selection python: ['type', 'text']">
|
||||||
<metal:row use-macro="template/macros/search_row" />
|
<metal:row use-macro="macros/search_row" />
|
||||||
</tal:block>
|
|
||||||
<tal:block define="search_selection string:text">
|
|
||||||
<metal:row use-macro="template/macros/search_row" />
|
|
||||||
</tal:block>
|
</tal:block>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<input type="button" name="search.add.1" value=" Add filter " />
|
<input type="button" value="Add concept filter" class="button" />
|
||||||
|
<input type="button" value="Add attribute filter" class="button" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">
|
||||||
|
<input type="submit" name="1.search" value="Search" class="submit"
|
||||||
|
tal:attributes="name string:$idPrefix.submit;
|
||||||
|
onclick python:
|
||||||
|
item.submitReplacing('1.results', formId, view)" />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
@ -26,9 +35,11 @@
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div metal:define-macro="search_results">
|
<div metal:define-macro="search_results"
|
||||||
|
id="1.results">
|
||||||
<fieldset class="box">
|
<fieldset class="box">
|
||||||
Search results
|
Search results
|
||||||
|
<p tal:content="python:request.get('1.search.1.text', 'nothing')" />
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -36,9 +47,70 @@
|
||||||
</metal:search>
|
</metal:search>
|
||||||
|
|
||||||
|
|
||||||
<tr metal:define-macro="search_row" id="search.row.1.1">
|
<tr metal:define-macro="search_row" id="search.row.1.1"
|
||||||
|
tal:define="rowNum item/rowNum;
|
||||||
|
idPrefix string:$idPrefix.$rowNum;
|
||||||
|
selection search_selection | item/searchSelection"
|
||||||
|
tal:attributes="id string:$idPrefix.row">
|
||||||
|
<td style="width: 30px">
|
||||||
|
<input type="button" value="−"
|
||||||
|
title="Remove search parameter"
|
||||||
|
tal:condition="python: selection not in ['type', 'text']" />
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<input type="button" value="−" />
|
<div metal:use-macro="macros/?selection" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
<metal:text define-macro="type">
|
||||||
|
<div>
|
||||||
|
<h3>Type(s) to search for</h3>
|
||||||
|
<label for="text"
|
||||||
|
tal:attributes="for string:$idPrefix.text">Type:</label>
|
||||||
|
<select name="text"
|
||||||
|
tal:attributes="name string:$idPrefix.text;
|
||||||
|
id string:$idPrefix.text;">
|
||||||
|
<option value=".loops/concepts/topic">Topic</option>
|
||||||
|
<option value="loops.resource.Document">Document</option>
|
||||||
|
</select>
|
||||||
|
<input type="button" value="+"
|
||||||
|
title="Add type" />
|
||||||
|
</div>
|
||||||
|
</metal:text>
|
||||||
|
|
||||||
|
|
||||||
|
<metal:text define-macro="text">
|
||||||
|
<div>
|
||||||
|
<h3>Search text</h3>
|
||||||
|
<input type="checkbox" checked
|
||||||
|
name="title" id="title"
|
||||||
|
tal:attributes="name string:$idPrefix.title;
|
||||||
|
id string:$idPrefix.title;" />
|
||||||
|
<label for="title"
|
||||||
|
tal:attributes="for string:$idPrefix.title">Title</label>
|
||||||
|
<input type="checkbox"
|
||||||
|
name="full" id="full"
|
||||||
|
tal:attributes="name string:$idPrefix.full;
|
||||||
|
id string:$idPrefix.full;" />
|
||||||
|
<label for="full"
|
||||||
|
tal:attributes="for string:$idPrefix.full">Full text</label>
|
||||||
|
<label for="text"
|
||||||
|
tal:attributes="for string:$idPrefix.text">Search words:</label>
|
||||||
|
<input type="text" name="text"
|
||||||
|
tal:attributes="name string:$idPrefix.text;
|
||||||
|
id string:$idPrefix.text;" />
|
||||||
|
<input type="button" value="+"
|
||||||
|
title="Add search word" />
|
||||||
|
</div>
|
||||||
|
</metal:text>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- obsolete -->
|
||||||
|
<tr metal:define-macro="search_row_selectable" id="search.row.1.1">
|
||||||
|
<td>
|
||||||
|
<input type="button" value="−"
|
||||||
|
title="Remove search parameter" />
|
||||||
</td>
|
</td>
|
||||||
<td tal:define="selection search_selection | string:type">
|
<td tal:define="selection search_selection | string:type">
|
||||||
<select>
|
<select>
|
||||||
|
@ -54,31 +126,3 @@
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
<metal:text define-macro="text">
|
|
||||||
<div id="search.text.1.1">
|
|
||||||
<input type="checkbox"
|
|
||||||
name="search.text.title.1.1" id="search.text.title.1.1" />
|
|
||||||
<label for="search.text.title.1.1">Title</label>
|
|
||||||
<input type="checkbox"
|
|
||||||
name="search.text.full.1.1" id="search.text.full.1.1" />
|
|
||||||
<label for="search.text.full.1.1">Full text</label>
|
|
||||||
<label for="search.text.text.1.1">Search words:</label>
|
|
||||||
<input type="text" name="search.text.text.1.1" />
|
|
||||||
<input type="text" name="search.text.text.1.1" />
|
|
||||||
<input type="button" name="search.text.add.1.1" value="+" />
|
|
||||||
</div>
|
|
||||||
</metal:text>
|
|
||||||
|
|
||||||
|
|
||||||
<metal:text define-macro="type">
|
|
||||||
<div id="search.type.1.2">
|
|
||||||
<label for="search.text.1.1">Type:</label>
|
|
||||||
<select type="text" name="search.type.text.1.2">
|
|
||||||
<option value=".loops/concepts/topic">Topic</option>
|
|
||||||
<option value="loops.resource.Document">Document</option>
|
|
||||||
</select>
|
|
||||||
<input type="button" name="search.type.add.1.2" value="+" />
|
|
||||||
</div>
|
|
||||||
</metal:text>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue