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())
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,

View file

@ -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)

View file

@ -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>

View file

@ -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"

View file

@ -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" />&nbsp;
<span i18n:translate="">Title</span>
<input />
<input name="create.title" size="30"
tal:attributes="value nothing" />&nbsp;
<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"

View file

@ -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,))

View file

@ -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"

View file

@ -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.
"""