diff --git a/browser/node.py b/browser/node.py
index 0bc72ec..de67d4e 100644
--- a/browser/node.py
+++ b/browser/node.py
@@ -125,12 +125,23 @@ class NodeView(BaseView):
return [NodeView(child, self.request)
for child in self.context.getMenuItems()]
+ @Lazy
+ def parents(self):
+ return zapi.getParents(self.context)
+
+ @Lazy
+ def nearestMenuItem(self):
+ if self.context.isMenuItem():
+ return self.context
+ for p in self.parents:
+ if p.isMenuItem():
+ return p
+
def selected(self, item):
- if item.context == self.context:
- return True
- if item.context in zapi.getParents(self.context) and not item.menuItems:
- return True
- return False
+ return item.context == self.nearestMenuItem
+
+ def active(self, item):
+ return item.context == self.context or item.context in self.parents
def targetDefaultView(self):
target = self.request.annotations.get('loops.view', {}).get('target')
@@ -149,6 +160,7 @@ class NodeView(BaseView):
if target is not None:
return zapi.getUtility(IIntIds).getId(target)
+
class ConfigureView(NodeView):
""" An editing view for configuring a node, optionally creating
a target object.
diff --git a/concept.py b/concept.py
index f26a99c..2556599 100644
--- a/concept.py
+++ b/concept.py
@@ -28,6 +28,7 @@ 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 alsoProvides, directlyProvides, directlyProvidedBy
from zope import schema
from zope.security.proxy import removeSecurityProxy
from persistent import Persistent
@@ -36,7 +37,7 @@ from cybertools.relation import DyadicRelation
from cybertools.relation.registry import getRelations
from cybertools.relation.registry import getRelationSingle, setRelationSingle
from cybertools.relation.interfaces import IRelationRegistry, IRelatable
-from cybertools.typology.interfaces import ITypeManager
+from cybertools.typology.interfaces import IType, ITypeManager
from interfaces import IConcept, IConceptRelation, IConceptView
from interfaces import IConceptManager, IConceptManagerContained
diff --git a/configure.zcml b/configure.zcml
index 0052a1a..ff295aa 100644
--- a/configure.zcml
+++ b/configure.zcml
@@ -240,6 +240,8 @@
+
+
diff --git a/helpers.txt b/helpers.txt
index cb37bec..d800eeb 100755
--- a/helpers.txt
+++ b/helpers.txt
@@ -190,3 +190,40 @@ condition:
>>> sorted(t.token for t in types)
['loops.resource.Document', 'loops.resource.MediaAsset']
+Type-based interfaces
+---------------------
+
+A type has an optional typeInterface attribute that objects of this type
+will be adaptable to. The default for this is None:
+
+ >>> cc1_type.typeInterface is None
+ True
+
+For concept objects that provide types (type providers) the value of
+the typeInterface attribute is the ITypeConcept interface:
+
+ >>> from loops.interfaces import ITypeConcept
+ >>> topic_type.typeInterface is ITypeConcept
+ True
+
+We now want to have a topic (i.e. a concept that has topic_type as its type)
+to provide the interface ITopic. This is done by assigning this interface
+to topic_type.typeProvider, i.e. the 'topic' concept, via an adapter:
+
+ >>> from loops.type import TypeConcept
+ >>> ztapi.provideAdapter(IConcept, ITypeConcept, TypeConcept)
+
+ >>> class ITopic(Interface): pass
+ >>> from zope.interface import implements
+ >>> class Topic(object):
+ ... implements(ITopic)
+ ... def __init__(self, context): pass
+ >>> ztapi.provideAdapter(IConcept, ITopic, Topic)
+
+ >>> ITypeConcept(topic).typeInterface = ITopic
+ >>> cc1.conceptType = topic
+
+ >>> cc1_type = IType(cc1)
+ >>> cc1Adapter = cc1_type.typeInterface(cc1)
+ >>> ITopic.providedBy(cc1Adapter)
+ True
diff --git a/interfaces.py b/interfaces.py
index ea8c52b..bfcb5d6 100644
--- a/interfaces.py
+++ b/interfaces.py
@@ -384,6 +384,10 @@ class INode(IView, IBaseNode):
a menu).
"""
+ def isMenuItem():
+ """ Return True if this object is a menu item.
+ """
+
def getTextItems():
""" Return the text items belonging to this object.
"""
@@ -465,3 +469,13 @@ class IIndexAttributes(Interface):
""" Return a string that identifies the type of the object.
"""
+
+# types stuff
+
+class ITypeConcept(Interface):
+ """ This interface should be provided by concepts of type 'type'.
+ """
+
+ typeInterface = Attribute('The interface provided by objects of this type')
+
+
diff --git a/type.py b/type.py
index 6812874..2673534 100644
--- a/type.py
+++ b/type.py
@@ -24,10 +24,12 @@ $Id$
from zope.app import zapi
from zope.component import adapts
+from zope.interface import implements
from zope.cachedescriptors.property import Lazy
from zope.dottedname.resolve import resolve
from cybertools.typology.type import BaseType, TypeManager
from loops.interfaces import ILoopsObject, IConcept, IResource
+from loops.interfaces import ITypeConcept
from loops.concept import Concept
from loops.resource import Document, MediaAsset
@@ -72,6 +74,17 @@ class ConceptType(LoopsType):
def defaultContainer(self):
return self.root.getConceptManager()
+ @Lazy
+ def typeInterface(self):
+ adapter = zapi.queryAdapter(self.typeProvider, ITypeConcept)
+ if adapter is not None:
+ return adapter.typeInterface
+ else:
+ conceptType = self.context.conceptType
+ if conceptType is conceptType.getConceptManager().getTypeConcept():
+ return ITypeConcept
+ return None
+
class ConceptTypeInfo(ConceptType):
@@ -161,3 +174,20 @@ class LoopsTypeManager(TypeManager):
def resourceTypes(self):
return tuple([ResourceTypeInfo(self.context, cls)
for cls in (Document, MediaAsset)])
+
+
+class TypeConcept(object):
+
+ implements(ITypeConcept)
+ adapts(IConcept)
+
+ def __init__(self, context):
+ self.context = context
+
+ def getTypeInterface(self):
+ return getattr(self.context, '_typeInterface', None)
+ def setTypeInterface(self, ifc):
+ self.context._typeInterface = ifc
+ typeInterface = property(getTypeInterface, setTypeInterface)
+
+
diff --git a/view.py b/view.py
index 53a7755..8ae4b64 100644
--- a/view.py
+++ b/view.py
@@ -128,7 +128,9 @@ class Node(View, OrderedContainer):
continue
def getMenu(self):
- return self.nodeType == 'menu' and self or self.getParentNode(['menu'])
+ if self.nodeType == 'menu':
+ return self
+ return self.getParentNode(['menu'])
def getPage(self):
pageTypes = ['page', 'menu', 'info']
@@ -142,6 +144,9 @@ class Node(View, OrderedContainer):
def getTextItems(self):
return self.getChildNodes(['text'])
+ def isMenuItem(self):
+ return self.nodeType in ('page', 'menu')
+
class ViewManager(OrderedContainer):
@@ -171,13 +176,14 @@ class NodeTraverser(ItemTraverser):
return self.context.getLoopsRoot()
if name.startswith('.target'):
if len(name) > len('.target'):
- idx = int(name[len('.target'):])
- target = zapi.getUtility(IIntIds).getObject(idx)
+ uid = int(name[len('.target'):])
+ target = zapi.getUtility(IIntIds).getObject(uid)
else:
target = self.context.target
- viewAnnotations = request.annotations.get('loops.view', {})
- viewAnnotations['target'] = target
- request.annotations['loops.view'] = viewAnnotations
- return self.context
+ if target is not None:
+ viewAnnotations = request.annotations.get('loops.view', {})
+ viewAnnotations['target'] = target
+ request.annotations['loops.view'] = viewAnnotations
+ return self.context
return super(NodeTraverser, self).publishTraverse(request, name)