Now concepts may have a type, and the types are concepts themselves, with the 'type' concept on top
git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@1081 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
parent
da52ecc17c
commit
19e4431cfb
8 changed files with 255 additions and 70 deletions
61
README.txt
61
README.txt
|
@ -72,6 +72,33 @@ We can now ask our concepts for their related child and parent concepts:
|
|||
>>> len(cc2.getChildren())
|
||||
0
|
||||
|
||||
Each concept should have a concept type; this is in fact provided by a
|
||||
relation to a special kind of concept object with the magic name 'type'.
|
||||
This type object is its own type:
|
||||
|
||||
>>> concepts['type'] = Concept(u'Type')
|
||||
>>> typeObject = concepts['type']
|
||||
>>> typeObject.setConceptType(typeObject)
|
||||
>>> typeObject.getConceptType().title
|
||||
u'Type'
|
||||
|
||||
>>> concepts['unknown'] = Concept(u'Unknown Type')
|
||||
>>> unknown = concepts['unknown']
|
||||
>>> unknown.setConceptType(typeObject)
|
||||
>>> unknown.getConceptType().title
|
||||
u'Type'
|
||||
|
||||
>>> cc1.setConceptType(unknown)
|
||||
>>> cc1.getConceptType().title
|
||||
u'Unknown Type'
|
||||
|
||||
>>> concepts['topic'] = Concept(u'Topic')
|
||||
>>> topic = concepts['topic']
|
||||
>>> topic.setConceptType(typeObject)
|
||||
>>> cc1.setConceptType(topic)
|
||||
>>> cc1.getConceptType().title
|
||||
u'Topic'
|
||||
|
||||
Concept Views
|
||||
-------------
|
||||
|
||||
|
@ -84,18 +111,6 @@ Concept Views
|
|||
The concept view allows to get a list of terms (sort of vocabulary) that
|
||||
can be used to show the objects in a listing:
|
||||
|
||||
>>> from zope.publisher.interfaces.browser import IBrowserRequest
|
||||
>>> from zope.app.form.browser.interfaces import ITerms
|
||||
>>> from loops.concept import ConceptSourceList
|
||||
>>> from loops.browser.common import LoopsTerms
|
||||
>>> ztapi.provideAdapter(ConceptSourceList, ITerms, LoopsTerms,
|
||||
... with=(IBrowserRequest,))
|
||||
>>> voc = view.getVocabularyForRelated()
|
||||
>>> for term in voc:
|
||||
... print term.token, term.title
|
||||
.loops/concepts/cc1 cc1
|
||||
.loops/concepts/cc2 Zope 3
|
||||
|
||||
The concept view allows updating the underlying context object:
|
||||
|
||||
>>> cc3 = Concept(u'loops for Zope 3')
|
||||
|
@ -115,6 +130,23 @@ The concept view allows updating the underlying context object:
|
|||
>>> sorted(c.title for c in cc1.getChildren())
|
||||
[u'loops for Zope 3']
|
||||
|
||||
We can also create a new concept and assign it:
|
||||
|
||||
>>> params = {'action': 'create', 'create.name': 'cc4',
|
||||
... 'create.title': u'New concept'}
|
||||
>>> view = ConceptView(cc1, TestRequest(**params))
|
||||
>>> view.update()
|
||||
True
|
||||
>>> sorted(c.title for c in cc1.getChildren())
|
||||
[u'New concept', u'loops for Zope 3']
|
||||
|
||||
Searchable Text Adapter
|
||||
-----------------------
|
||||
|
||||
>>> from loops.concept import SearchableText
|
||||
>>> SearchableText(cc2).searchableText()
|
||||
u'cc2 Zope 3'
|
||||
|
||||
Resources and what they have to do with Concepts
|
||||
================================================
|
||||
|
||||
|
@ -356,9 +388,9 @@ objects.) The source is basically a source list:
|
|||
>>> from loops.target import TargetSourceList
|
||||
>>> source = TargetSourceList(m111)
|
||||
>>> len(source)
|
||||
4
|
||||
8
|
||||
>>> sorted([zapi.getName(s) for s in source])
|
||||
[u'cc1', u'cc2', u'cc3', u'doc1']
|
||||
[u'cc1', u'cc2', u'cc3', u'cc4', u'doc1', u'topic', u'type', u'unknown']
|
||||
|
||||
The form then uses a sort of browser view providing the ITerms interface
|
||||
based on this source list:
|
||||
|
@ -428,6 +460,7 @@ A node's target is rendered using the NodeView's renderTargetBody()
|
|||
method. This makes use of a browser view registered for the target interface,
|
||||
and of a lot of other stuff needed for the rendering machine.
|
||||
|
||||
>>> from zope.publisher.interfaces.browser import IBrowserRequest
|
||||
>>> from zope.app.publisher.interfaces.browser import IBrowserView
|
||||
>>> from loops.browser.resource import DocumentView
|
||||
>>> ztapi.provideAdapter(IDocument, Interface, DocumentView,
|
||||
|
|
|
@ -23,6 +23,7 @@ $Id$
|
|||
"""
|
||||
|
||||
from zope.app import zapi
|
||||
from zope.app.catalog.interfaces import ICatalog
|
||||
from zope.app.dublincore.interfaces import ICMFDublinCore
|
||||
from zope.app.form.browser.interfaces import ITerms
|
||||
from zope.cachedescriptors.property import Lazy
|
||||
|
@ -30,26 +31,32 @@ from zope.interface import implements
|
|||
from zope.publisher.interfaces import BadRequest
|
||||
from zope import schema
|
||||
from zope.security.proxy import removeSecurityProxy
|
||||
from loops.concept import ConceptSourceList
|
||||
from loops.concept import Concept
|
||||
from loops.browser.common import BaseView, LoopsTerms
|
||||
|
||||
|
||||
class ConceptView(BaseView):
|
||||
|
||||
def children(self):
|
||||
request = self.request
|
||||
for c in self.context.getChildren():
|
||||
yield ConceptView(c, request)
|
||||
ch = self.context.getChildren()
|
||||
return ch and self.viewIterator(ch) or []
|
||||
|
||||
def parents(self):
|
||||
p = self.context.getParents()
|
||||
return p and self.viewIterator(p) or []
|
||||
|
||||
def viewIterator(self, objs):
|
||||
request = self.request
|
||||
for c in self.context.getParents():
|
||||
yield ConceptView(c, request)
|
||||
for o in objs:
|
||||
yield ConceptView(o, request)
|
||||
|
||||
def update(self):
|
||||
action = self.request.get('action', None)
|
||||
action = self.request.get('action')
|
||||
if action is None:
|
||||
return True
|
||||
if action == 'create':
|
||||
self.createAndAssign()
|
||||
return True
|
||||
tokens = self.request.get('tokens', [])
|
||||
for token in tokens:
|
||||
concept = self.loopsRoot.loopsTraverse(token)
|
||||
|
@ -62,7 +69,7 @@ class ConceptView(BaseView):
|
|||
else:
|
||||
raise(BadRequest, 'Illegal assignAs parameter: %s.' % assignAs)
|
||||
elif action == 'remove':
|
||||
qualifier = self.request.get('qualifier', None)
|
||||
qualifier = self.request.get('qualifier')
|
||||
if qualifier == 'parents':
|
||||
self.context.deassignParents(concept)
|
||||
elif qualifier == 'children':
|
||||
|
@ -73,9 +80,39 @@ class ConceptView(BaseView):
|
|||
raise(BadRequest, 'Illegal action: %s.' % action)
|
||||
return True
|
||||
|
||||
def getVocabularyForRelated(self):
|
||||
source = ConceptSourceList(self.context)
|
||||
terms = zapi.getMultiAdapter((source, self.request), ITerms)
|
||||
for candidate in source:
|
||||
yield terms.getTerm(candidate)
|
||||
def createAndAssign(self):
|
||||
request = self.request
|
||||
name = request.get('create.name')
|
||||
if not name:
|
||||
raise(BadRequest, 'Empty name.')
|
||||
title = request.get('create.title', u'')
|
||||
type = request.get('create.type')
|
||||
concept = Concept(title)
|
||||
container = self.loopsRoot.getConceptManager()
|
||||
container[name] = concept
|
||||
assignAs = self.request.get('assignAs', 'child')
|
||||
if assignAs == 'child':
|
||||
self.context.assignChild(removeSecurityProxy(concept))
|
||||
elif assignAs == 'parent':
|
||||
self.context.assignParent(removeSecurityProxy(concept))
|
||||
else:
|
||||
raise(BadRequest, 'Illegal assignAs parameter: %s.' % assignAs)
|
||||
|
||||
#def getVocabularyForRelated(self): # obsolete
|
||||
# source = ConceptSourceList(self.context)
|
||||
# terms = zapi.getMultiAdapter((source, self.request), ITerms)
|
||||
# for candidate in source:
|
||||
# yield terms.getTerm(candidate)
|
||||
|
||||
def search(self):
|
||||
request = self.request
|
||||
if request.get('action') != 'search':
|
||||
return []
|
||||
searchTerm = request.get('searchTerm', None)
|
||||
if searchTerm:
|
||||
cat = zapi.getUtility(ICatalog)
|
||||
result = cat.searchResults(loops_searchableText=searchTerm)
|
||||
else:
|
||||
result = self.loopsRoot.getConceptManager().values()
|
||||
return self.viewIterator(result)
|
||||
|
||||
|
|
|
@ -7,36 +7,38 @@
|
|||
|
||||
<h1 tal:content="context/title">Concept Title</h1><br />
|
||||
|
||||
<tal:block define="items view/parents;
|
||||
<div tal:define="items view/parents;
|
||||
action string:remove;
|
||||
qualifier string:parents;
|
||||
summary string:Currently assigned objects;
|
||||
legend string:Parent Concepts;
|
||||
buttonText string:Remove Assignment(s);
|
||||
">
|
||||
buttonText string:Remove Parents;"
|
||||
style="float:left; padding-right:20px">
|
||||
<metal:parents use-macro="views/relation_macros/listing" />
|
||||
</tal:block>
|
||||
<tal:block define="items view/children;
|
||||
</div>
|
||||
<div tal:define="items view/children;
|
||||
action string:remove;
|
||||
qualifier string:children;
|
||||
summary string:Currently assigned objects;
|
||||
legend string:Child Concepts;
|
||||
buttonText string:Remove Assignment(s);
|
||||
">
|
||||
buttonText string:Remove Children;"
|
||||
style="padding-right:20px">
|
||||
<metal:children use-macro="views/relation_macros/listing" />
|
||||
</tal:block>
|
||||
</div>
|
||||
|
||||
<tal:block>
|
||||
<div tal:define="legend string:Create Concept;
|
||||
buttonText string:Create Concept"
|
||||
style="padding-right:20px; clear:left">
|
||||
<metal:create use-macro="views/relation_macros/create" />
|
||||
</tal:block>
|
||||
</div>
|
||||
|
||||
<tal:block define="items view/getVocabularyForRelated;
|
||||
<div tal:define="items view/search;
|
||||
action string:assign;
|
||||
qualifier nothing;
|
||||
summary string:Assignment candidates;
|
||||
legend string:Search Results;
|
||||
buttonText string:Assign;
|
||||
">
|
||||
legend string:Search;
|
||||
buttonText string:Assign;"
|
||||
style="padding-right:20px">
|
||||
<metal:assign use-macro="views/relation_macros/listing">
|
||||
<metal:search fill-slot="topActions">
|
||||
<metal:block use-macro="views/relation_macros/search" />
|
||||
|
@ -44,13 +46,13 @@
|
|||
<metal:special fill-slot="specialButtons">
|
||||
as
|
||||
<select name="assignAs">
|
||||
<option value="child" selected i18n:translate="">child</option>
|
||||
<option value="parent" i18n:translate="">parent</option>
|
||||
<option value="child" selected i18n:translate="">Child</option>
|
||||
<option value="parent" i18n:translate="">Parent</option>
|
||||
</select>
|
||||
object(s)
|
||||
</metal:special>
|
||||
</metal:assign>
|
||||
</tal:block>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
|
|
|
@ -415,11 +415,11 @@
|
|||
<!-- vocabulary, traversing, and other stuff -->
|
||||
|
||||
<zope:adapter factory="loops.browser.common.LoopsTerms"
|
||||
for="loops.target.TargetSourceList
|
||||
for="loops.concept.ConceptTypeSourceList
|
||||
zope.publisher.interfaces.browser.IBrowserRequest" />
|
||||
|
||||
<zope:adapter factory="loops.browser.common.LoopsTerms"
|
||||
for="loops.concept.ConceptSourceList
|
||||
for="loops.target.TargetSourceList
|
||||
zope.publisher.interfaces.browser.IBrowserRequest" />
|
||||
|
||||
<zope:view factory="loops.view.NodeTraverser"
|
||||
|
|
|
@ -4,9 +4,10 @@
|
|||
<metal:assignments define-macro="listing">
|
||||
<fieldset>
|
||||
<legend tal:content="legend"
|
||||
i18n:translate="">Parent Concepts</legend>
|
||||
i18n:translate="">Listing</legend>
|
||||
<metal:top define-slot="topActions" />
|
||||
<form method="post" name="listing" action="."
|
||||
<form metal:define-macro="listing_form"
|
||||
method="post" name="listing" action="."
|
||||
tal:attributes="action request/URL"
|
||||
tal:condition="items">
|
||||
<input type="hidden" name="action" value="remove"
|
||||
|
@ -53,27 +54,33 @@
|
|||
|
||||
<metal:create define-macro="create">
|
||||
<fieldset>
|
||||
<legend i18n:translate="">Create Object</legend>
|
||||
<legend tal:content="legend"
|
||||
i18n:translate="">Create Object</legend>
|
||||
<form method="post" name="listing" action="."
|
||||
tal:attributes="action request/URL">
|
||||
<input type="hidden" name="action" value="create" />
|
||||
<div class="row">
|
||||
<span i18n:translate="">Name</span>
|
||||
<input />
|
||||
<input name="create.name" size="15"
|
||||
tal:attributes="value nothing" />
|
||||
<span i18n:translate="">Title</span>
|
||||
<input />
|
||||
<input name="create.title" size="30"
|
||||
tal:attributes="value nothing" />
|
||||
<span i18n:translate="">Type</span>
|
||||
<select>
|
||||
<option>Topic</option>
|
||||
<select name="create.type">
|
||||
<option value=".loops/concepts/topic"
|
||||
i18n:translate="">Topic</option>
|
||||
</select>
|
||||
</div>
|
||||
</div><br />
|
||||
<div class="formControls">
|
||||
<input class="context" type="submit" name="form.button.submit"
|
||||
value="Create Object"
|
||||
i18n:attributes="value" />
|
||||
i18n:attributes="value"
|
||||
tal:attributes="value buttonText" />
|
||||
and assign as
|
||||
<select name="assignAs">
|
||||
<option value="child" selected i18n:translate="">child</option>
|
||||
<option value="parent" i18n:translate="">parent</option>
|
||||
<option value="child" selected i18n:translate="">Child</option>
|
||||
<option value="parent" i18n:translate="">Parent</option>
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -84,9 +91,17 @@
|
|||
<metal:search define-macro="search">
|
||||
<form method="post" name="listing" action="."
|
||||
tal:attributes="action request/URL">
|
||||
<input type="hidden" name="action" value="search" />
|
||||
<div class="row">
|
||||
<span i18n:translate="">Search Term</span>
|
||||
<input />
|
||||
<input name="searchTerm"
|
||||
tal:attributes="value request/searchTerm | nothing" />
|
||||
<span i18n:translate="">Type</span>
|
||||
<select name="search.type">
|
||||
<option value="*">Any</option>
|
||||
<option value=".loops/concepts/topic"
|
||||
i18n:translate="">Topic</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="formControls">
|
||||
<input class="context" type="submit" name="form.button.submit"
|
||||
|
|
66
concept.py
66
concept.py
|
@ -25,6 +25,8 @@ $Id$
|
|||
from zope.app import zapi
|
||||
from zope.app.container.btree import BTreeContainer
|
||||
from zope.app.container.contained import Contained
|
||||
from zope.cachedescriptors.property import Lazy
|
||||
from zope.component import adapts
|
||||
from zope.interface import implements
|
||||
from zope import schema
|
||||
from zope.security.proxy import removeSecurityProxy
|
||||
|
@ -32,11 +34,13 @@ from persistent import Persistent
|
|||
|
||||
from cybertools.relation import DyadicRelation
|
||||
from cybertools.relation.registry import getRelations
|
||||
from cybertools.relation.registry import getRelationSingle, setRelationSingle
|
||||
from cybertools.relation.interfaces import IRelationRegistry
|
||||
|
||||
from interfaces import IConcept, IConceptView
|
||||
from interfaces import IConcept, IConceptRelation, IConceptView
|
||||
from interfaces import IConceptManager, IConceptManagerContained
|
||||
from interfaces import ILoopsContained
|
||||
from interfaces import ISearchableText
|
||||
|
||||
|
||||
# relation classes
|
||||
|
@ -44,11 +48,20 @@ from interfaces import ILoopsContained
|
|||
class ConceptRelation(DyadicRelation):
|
||||
""" A relation between concept objects.
|
||||
"""
|
||||
implements(IConceptRelation)
|
||||
|
||||
|
||||
class TypeRelation(DyadicRelation):
|
||||
""" A special relation between two concepts, the parent specifying
|
||||
the type of the child.
|
||||
"""
|
||||
implements(IConceptRelation)
|
||||
|
||||
|
||||
class ResourceRelation(DyadicRelation):
|
||||
""" A relation between a concept and a resource object.
|
||||
"""
|
||||
implements(IConceptRelation)
|
||||
|
||||
|
||||
# concept
|
||||
|
@ -64,6 +77,14 @@ class Concept(Contained, Persistent):
|
|||
def setTitle(self, title): self._title = title
|
||||
title = property(getTitle, setTitle)
|
||||
|
||||
def getConceptType(self):
|
||||
rel = getRelationSingle(self, TypeRelation)
|
||||
return rel and rel.second or None
|
||||
def setConceptType(self, concept):
|
||||
if self.getConceptType() != concept:
|
||||
setRelationSingle(TypeRelation(self, removeSecurityProxy(concept)))
|
||||
conceptType = property(getConceptType, setConceptType)
|
||||
|
||||
def __init__(self, title=u''):
|
||||
self.title = title
|
||||
|
||||
|
@ -165,3 +186,46 @@ class ConceptSourceList(object):
|
|||
return len(self.concepts)
|
||||
|
||||
|
||||
class ConceptTypeSourceList(object):
|
||||
|
||||
implements(schema.interfaces.IIterableSource)
|
||||
|
||||
def __init__(self, context):
|
||||
self.context = context
|
||||
#self.context = removeSecurityProxy(context)
|
||||
root = self.context.getLoopsRoot()
|
||||
self.concepts = root.getConceptManager()
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.conceptTypes)
|
||||
|
||||
@Lazy
|
||||
def conceptTypes(self):
|
||||
result = []
|
||||
typeObject = self.concepts.get('type')
|
||||
unknownType = self.concepts.get('unknown')
|
||||
if typeObject is not None:
|
||||
types = typeObject.getParents((TypeRelation,))
|
||||
if typeObject not in types:
|
||||
result.append(typeObject)
|
||||
if unknownType is not None and unknownType not in types:
|
||||
result.append(unknownType)
|
||||
result.extend(types)
|
||||
return result
|
||||
|
||||
def __len__(self):
|
||||
return len(self.conceptTypes)
|
||||
|
||||
|
||||
class SearchableText(object):
|
||||
|
||||
implements(ISearchableText)
|
||||
adapts(IConcept)
|
||||
|
||||
def __init__(self, context):
|
||||
self.context = context
|
||||
|
||||
def searchableText(self):
|
||||
context = self.context
|
||||
return ' '.join((zapi.getName(context), context.title,))
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
|
||||
<require
|
||||
permission="zope.View"
|
||||
attributes="getLoopsUri loopsTraverse" />
|
||||
attributes="getLoopsUri loopsTraverse getConceptManager" />
|
||||
|
||||
</content>
|
||||
|
||||
|
@ -227,6 +227,8 @@
|
|||
|
||||
<!-- adapters -->
|
||||
|
||||
<adapter factory="loops.concept.SearchableText" />
|
||||
|
||||
<adapter factory="loops.external.NodesLoader" />
|
||||
<adapter factory="loops.external.NodesExporter" />
|
||||
<adapter factory="loops.external.NodesImporter" />
|
||||
|
@ -241,6 +243,11 @@
|
|||
<adapter factory="loops.target.ConceptProxy"
|
||||
permission="zope.ManageContent" />
|
||||
|
||||
<vocabulary
|
||||
factory="loops.concept.ConceptTypeSourceList"
|
||||
name="loops.conceptTypeSource"
|
||||
/>
|
||||
|
||||
<vocabulary
|
||||
factory="loops.target.TargetSourceList"
|
||||
name="loops.targetSource"
|
||||
|
|
|
@ -71,6 +71,14 @@ class IConcept(ILoopsObject, IPotentialTarget):
|
|||
default=u'',
|
||||
required=False)
|
||||
|
||||
conceptType = schema.Choice(
|
||||
title=_(u'Concept Type'),
|
||||
description=_(u"The type of the concept, specified by a relation to "
|
||||
"a concept of type 'type'."),
|
||||
default=None,
|
||||
source="loops.conceptTypeSource",
|
||||
required=False)
|
||||
|
||||
def getChildren(relationships=None):
|
||||
""" Return a sequence of concepts related to self as child concepts,
|
||||
possibly restricted to the relationships (typically a list of
|
||||
|
@ -371,12 +379,6 @@ class INodeConfigSchema(INode, ITargetProperties):
|
|||
required=False)
|
||||
|
||||
|
||||
class ITargetRelation(IRelation):
|
||||
""" (Marker) interfaces for relations pointing to a target
|
||||
of a view or node.
|
||||
"""
|
||||
|
||||
|
||||
# the loops top-level container
|
||||
|
||||
#class ILoops(ILoopsObject, IFolder):
|
||||
|
@ -410,3 +412,28 @@ class ILoops(ILoopsObject):
|
|||
class ILoopsContained(Interface):
|
||||
containers(ILoops)
|
||||
|
||||
|
||||
# relation interfaces
|
||||
|
||||
class ITargetRelation(IRelation):
|
||||
""" (Marker) interfaces for relations pointing to a target
|
||||
of a view or node.
|
||||
"""
|
||||
|
||||
|
||||
class IConceptRelation(IRelation):
|
||||
""" (Marker) interfaces for relations originating from a concept.
|
||||
"""
|
||||
|
||||
|
||||
# interfaces for catalog indexes
|
||||
|
||||
class ISearchableText(Interface):
|
||||
""" Objects to be included in the general full-text index should provide
|
||||
or be adaptable to this interface.
|
||||
"""
|
||||
|
||||
def searchableText():
|
||||
""" Return a text with all parts to be indexed by a full-text index.
|
||||
"""
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue