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:
helmutm 2006-02-15 14:49:27 +00:00
parent da52ecc17c
commit 19e4431cfb
8 changed files with 255 additions and 70 deletions

View file

@ -72,6 +72,33 @@ We can now ask our concepts for their related child and parent concepts:
>>> len(cc2.getChildren()) >>> len(cc2.getChildren())
0 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 Concept Views
------------- -------------
@ -84,18 +111,6 @@ Concept Views
The concept view allows to get a list of terms (sort of vocabulary) that The concept view allows to get a list of terms (sort of vocabulary) that
can be used to show the objects in a listing: 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: The concept view allows updating the underlying context object:
>>> cc3 = Concept(u'loops for Zope 3') >>> 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()) >>> sorted(c.title for c in cc1.getChildren())
[u'loops for Zope 3'] [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 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 >>> from loops.target import TargetSourceList
>>> source = TargetSourceList(m111) >>> source = TargetSourceList(m111)
>>> len(source) >>> len(source)
4 8
>>> sorted([zapi.getName(s) for s in source]) >>> 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 The form then uses a sort of browser view providing the ITerms interface
based on this source list: 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, 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. 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 zope.app.publisher.interfaces.browser import IBrowserView
>>> from loops.browser.resource import DocumentView >>> from loops.browser.resource import DocumentView
>>> ztapi.provideAdapter(IDocument, Interface, DocumentView, >>> ztapi.provideAdapter(IDocument, Interface, DocumentView,

View file

@ -23,6 +23,7 @@ $Id$
""" """
from zope.app import zapi from zope.app import zapi
from zope.app.catalog.interfaces import ICatalog
from zope.app.dublincore.interfaces import ICMFDublinCore from zope.app.dublincore.interfaces import ICMFDublinCore
from zope.app.form.browser.interfaces import ITerms from zope.app.form.browser.interfaces import ITerms
from zope.cachedescriptors.property import Lazy from zope.cachedescriptors.property import Lazy
@ -30,26 +31,32 @@ from zope.interface import implements
from zope.publisher.interfaces import BadRequest from zope.publisher.interfaces import BadRequest
from zope import schema from zope import schema
from zope.security.proxy import removeSecurityProxy from zope.security.proxy import removeSecurityProxy
from loops.concept import ConceptSourceList from loops.concept import Concept
from loops.browser.common import BaseView, LoopsTerms from loops.browser.common import BaseView, LoopsTerms
class ConceptView(BaseView): class ConceptView(BaseView):
def children(self): def children(self):
request = self.request ch = self.context.getChildren()
for c in self.context.getChildren(): return ch and self.viewIterator(ch) or []
yield ConceptView(c, request)
def parents(self): def parents(self):
p = self.context.getParents()
return p and self.viewIterator(p) or []
def viewIterator(self, objs):
request = self.request request = self.request
for c in self.context.getParents(): for o in objs:
yield ConceptView(c, request) yield ConceptView(o, request)
def update(self): def update(self):
action = self.request.get('action', None) action = self.request.get('action')
if action is None: if action is None:
return True return True
if action == 'create':
self.createAndAssign()
return True
tokens = self.request.get('tokens', []) tokens = self.request.get('tokens', [])
for token in tokens: for token in tokens:
concept = self.loopsRoot.loopsTraverse(token) concept = self.loopsRoot.loopsTraverse(token)
@ -62,7 +69,7 @@ class ConceptView(BaseView):
else: else:
raise(BadRequest, 'Illegal assignAs parameter: %s.' % assignAs) raise(BadRequest, 'Illegal assignAs parameter: %s.' % assignAs)
elif action == 'remove': elif action == 'remove':
qualifier = self.request.get('qualifier', None) qualifier = self.request.get('qualifier')
if qualifier == 'parents': if qualifier == 'parents':
self.context.deassignParents(concept) self.context.deassignParents(concept)
elif qualifier == 'children': elif qualifier == 'children':
@ -73,9 +80,39 @@ class ConceptView(BaseView):
raise(BadRequest, 'Illegal action: %s.' % action) raise(BadRequest, 'Illegal action: %s.' % action)
return True return True
def getVocabularyForRelated(self): def createAndAssign(self):
source = ConceptSourceList(self.context) request = self.request
terms = zapi.getMultiAdapter((source, self.request), ITerms) name = request.get('create.name')
for candidate in source: if not name:
yield terms.getTerm(candidate) 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)

View file

@ -7,36 +7,38 @@
<h1 tal:content="context/title">Concept Title</h1><br /> <h1 tal:content="context/title">Concept Title</h1><br />
<tal:block define="items view/parents; <div tal:define="items view/parents;
action string:remove; action string:remove;
qualifier string:parents; qualifier string:parents;
summary string:Currently assigned objects; summary string:Currently assigned objects;
legend string:Parent Concepts; 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" /> <metal:parents use-macro="views/relation_macros/listing" />
</tal:block> </div>
<tal:block define="items view/children; <div tal:define="items view/children;
action string:remove; action string:remove;
qualifier string:children; qualifier string:children;
summary string:Currently assigned objects; summary string:Currently assigned objects;
legend string:Child Concepts; 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" /> <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" /> <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; action string:assign;
qualifier nothing; qualifier nothing;
summary string:Assignment candidates; summary string:Assignment candidates;
legend string:Search Results; legend string:Search;
buttonText string:Assign; buttonText string:Assign;"
"> style="padding-right:20px">
<metal:assign use-macro="views/relation_macros/listing"> <metal:assign use-macro="views/relation_macros/listing">
<metal:search fill-slot="topActions"> <metal:search fill-slot="topActions">
<metal:block use-macro="views/relation_macros/search" /> <metal:block use-macro="views/relation_macros/search" />
@ -44,13 +46,13 @@
<metal:special fill-slot="specialButtons"> <metal:special fill-slot="specialButtons">
as as
<select name="assignAs"> <select name="assignAs">
<option value="child" selected i18n:translate="">child</option> <option value="child" selected i18n:translate="">Child</option>
<option value="parent" i18n:translate="">parent</option> <option value="parent" i18n:translate="">Parent</option>
</select> </select>
object(s) object(s)
</metal:special> </metal:special>
</metal:assign> </metal:assign>
</tal:block> </div>
</div> </div>
</body> </body>

View file

@ -415,11 +415,11 @@
<!-- vocabulary, traversing, and other stuff --> <!-- vocabulary, traversing, and other stuff -->
<zope:adapter factory="loops.browser.common.LoopsTerms" <zope:adapter factory="loops.browser.common.LoopsTerms"
for="loops.target.TargetSourceList for="loops.concept.ConceptTypeSourceList
zope.publisher.interfaces.browser.IBrowserRequest" /> zope.publisher.interfaces.browser.IBrowserRequest" />
<zope:adapter factory="loops.browser.common.LoopsTerms" <zope:adapter factory="loops.browser.common.LoopsTerms"
for="loops.concept.ConceptSourceList for="loops.target.TargetSourceList
zope.publisher.interfaces.browser.IBrowserRequest" /> zope.publisher.interfaces.browser.IBrowserRequest" />
<zope:view factory="loops.view.NodeTraverser" <zope:view factory="loops.view.NodeTraverser"

View file

@ -4,9 +4,10 @@
<metal:assignments define-macro="listing"> <metal:assignments define-macro="listing">
<fieldset> <fieldset>
<legend tal:content="legend" <legend tal:content="legend"
i18n:translate="">Parent Concepts</legend> i18n:translate="">Listing</legend>
<metal:top define-slot="topActions" /> <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:attributes="action request/URL"
tal:condition="items"> tal:condition="items">
<input type="hidden" name="action" value="remove" <input type="hidden" name="action" value="remove"
@ -53,27 +54,33 @@
<metal:create define-macro="create"> <metal:create define-macro="create">
<fieldset> <fieldset>
<legend i18n:translate="">Create Object</legend> <legend tal:content="legend"
i18n:translate="">Create Object</legend>
<form method="post" name="listing" action="." <form method="post" name="listing" action="."
tal:attributes="action request/URL"> tal:attributes="action request/URL">
<input type="hidden" name="action" value="create" />
<div class="row"> <div class="row">
<span i18n:translate="">Name</span> <span i18n:translate="">Name</span>
<input /> <input name="create.name" size="15"
tal:attributes="value nothing" />&nbsp;
<span i18n:translate="">Title</span> <span i18n:translate="">Title</span>
<input /> <input name="create.title" size="30"
tal:attributes="value nothing" />&nbsp;
<span i18n:translate="">Type</span> <span i18n:translate="">Type</span>
<select> <select name="create.type">
<option>Topic</option> <option value=".loops/concepts/topic"
i18n:translate="">Topic</option>
</select> </select>
</div> </div><br />
<div class="formControls"> <div class="formControls">
<input class="context" type="submit" name="form.button.submit" <input class="context" type="submit" name="form.button.submit"
value="Create Object" value="Create Object"
i18n:attributes="value" /> i18n:attributes="value"
tal:attributes="value buttonText" />
and assign as and assign as
<select name="assignAs"> <select name="assignAs">
<option value="child" selected i18n:translate="">child</option> <option value="child" selected i18n:translate="">Child</option>
<option value="parent" i18n:translate="">parent</option> <option value="parent" i18n:translate="">Parent</option>
</select> </select>
</div> </div>
</form> </form>
@ -84,9 +91,17 @@
<metal:search define-macro="search"> <metal:search define-macro="search">
<form method="post" name="listing" action="." <form method="post" name="listing" action="."
tal:attributes="action request/URL"> tal:attributes="action request/URL">
<input type="hidden" name="action" value="search" />
<div class="row"> <div class="row">
<span i18n:translate="">Search Term</span> <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>
<div class="formControls"> <div class="formControls">
<input class="context" type="submit" name="form.button.submit" <input class="context" type="submit" name="form.button.submit"

View file

@ -25,6 +25,8 @@ $Id$
from zope.app import zapi from zope.app import zapi
from zope.app.container.btree import BTreeContainer from zope.app.container.btree import BTreeContainer
from zope.app.container.contained import Contained 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.interface import implements
from zope import schema from zope import schema
from zope.security.proxy import removeSecurityProxy from zope.security.proxy import removeSecurityProxy
@ -32,11 +34,13 @@ from persistent import Persistent
from cybertools.relation import DyadicRelation from cybertools.relation import DyadicRelation
from cybertools.relation.registry import getRelations from cybertools.relation.registry import getRelations
from cybertools.relation.registry import getRelationSingle, setRelationSingle
from cybertools.relation.interfaces import IRelationRegistry 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 IConceptManager, IConceptManagerContained
from interfaces import ILoopsContained from interfaces import ILoopsContained
from interfaces import ISearchableText
# relation classes # relation classes
@ -44,11 +48,20 @@ from interfaces import ILoopsContained
class ConceptRelation(DyadicRelation): class ConceptRelation(DyadicRelation):
""" A relation between concept objects. """ 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): class ResourceRelation(DyadicRelation):
""" A relation between a concept and a resource object. """ A relation between a concept and a resource object.
""" """
implements(IConceptRelation)
# concept # concept
@ -64,6 +77,14 @@ class Concept(Contained, Persistent):
def setTitle(self, title): self._title = title def setTitle(self, title): self._title = title
title = property(getTitle, setTitle) 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''): def __init__(self, title=u''):
self.title = title self.title = title
@ -165,3 +186,46 @@ class ConceptSourceList(object):
return len(self.concepts) 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,))

View file

@ -49,7 +49,7 @@
<require <require
permission="zope.View" permission="zope.View"
attributes="getLoopsUri loopsTraverse" /> attributes="getLoopsUri loopsTraverse getConceptManager" />
</content> </content>
@ -227,6 +227,8 @@
<!-- adapters --> <!-- adapters -->
<adapter factory="loops.concept.SearchableText" />
<adapter factory="loops.external.NodesLoader" /> <adapter factory="loops.external.NodesLoader" />
<adapter factory="loops.external.NodesExporter" /> <adapter factory="loops.external.NodesExporter" />
<adapter factory="loops.external.NodesImporter" /> <adapter factory="loops.external.NodesImporter" />
@ -241,6 +243,11 @@
<adapter factory="loops.target.ConceptProxy" <adapter factory="loops.target.ConceptProxy"
permission="zope.ManageContent" /> permission="zope.ManageContent" />
<vocabulary
factory="loops.concept.ConceptTypeSourceList"
name="loops.conceptTypeSource"
/>
<vocabulary <vocabulary
factory="loops.target.TargetSourceList" factory="loops.target.TargetSourceList"
name="loops.targetSource" name="loops.targetSource"

View file

@ -71,6 +71,14 @@ class IConcept(ILoopsObject, IPotentialTarget):
default=u'', default=u'',
required=False) 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): def getChildren(relationships=None):
""" Return a sequence of concepts related to self as child concepts, """ Return a sequence of concepts related to self as child concepts,
possibly restricted to the relationships (typically a list of possibly restricted to the relationships (typically a list of
@ -371,12 +379,6 @@ class INodeConfigSchema(INode, ITargetProperties):
required=False) required=False)
class ITargetRelation(IRelation):
""" (Marker) interfaces for relations pointing to a target
of a view or node.
"""
# the loops top-level container # the loops top-level container
#class ILoops(ILoopsObject, IFolder): #class ILoops(ILoopsObject, IFolder):
@ -410,3 +412,28 @@ class ILoops(ILoopsObject):
class ILoopsContained(Interface): class ILoopsContained(Interface):
containers(ILoops) 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.
"""