diff --git a/browser/common.py b/browser/common.py index a476e5a..9b05eb6 100644 --- a/browser/common.py +++ b/browser/common.py @@ -52,7 +52,7 @@ class BaseView(object): skin = None if skinName and IView.providedBy(self.context): skin = zapi.queryUtility(ISkin, skinName) - if skin is not None: + if skin: applySkin(self.request, skin) self.skin = skin diff --git a/browser/configure.zcml b/browser/configure.zcml index ad1711d..1490ccb 100644 --- a/browser/configure.zcml +++ b/browser/configure.zcml @@ -565,4 +565,32 @@ allowed_interface="zope.publisher.interfaces.browser.IBrowserPublisher" permission="zope.Public" /> + + + + + + + + + + diff --git a/browser/node.py b/browser/node.py index 4b00e4f..c2745b5 100644 --- a/browser/node.py +++ b/browser/node.py @@ -22,8 +22,10 @@ View class for Node objects. $Id$ """ +from zope import component, interface from zope.cachedescriptors.property import Lazy from zope.app import zapi +from zope.app.annotation.interfaces import IAnnotations from zope.app.catalog.interfaces import ICatalog from zope.app.container.browser.contents import JustContents from zope.app.container.browser.adding import ContentAdding @@ -36,8 +38,10 @@ from zope.proxy import removeAllProxies from zope.security import canAccess, canWrite from zope.security.proxy import removeSecurityProxy +from cybertools.browser import configurator from cybertools.typology.interfaces import ITypeManager from loops.interfaces import IConcept, IResource, IDocument, IMediaAsset, INode +from loops.interfaces import IViewConfiguratorSchema from loops.resource import MediaAsset from loops import util from loops.browser.common import BaseView @@ -314,3 +318,43 @@ class NodeAdding(ContentAdding): # 'has_custom_add_view': True, # 'description': 'This creates a node with an associated document'}) return info + + +class ViewPropertiesConfigurator(object): + + interface.implements(IViewConfiguratorSchema) + component.adapts(INode) + + def __init__(self, context): + self.context = removeSecurityProxy(context) + + def setSkinName(self, skinName): + ann = IAnnotations(self.context) + setting = ann.get(configurator.ANNOTATION_KEY, {}) + setting['skinName'] = {'value': skinName} + ann[configurator.ANNOTATION_KEY] = setting + def getSkinName(self): + ann = IAnnotations(self.context) + setting = ann.get(configurator.ANNOTATION_KEY, {}) + return setting.get('skinName', {}).get('value', '') + skinName = property(getSkinName, setSkinName) + + +class NodeViewConfigurator(configurator.ViewConfigurator): + """ Take properties from next menu item... + """ + + @property + def viewProperties(self): + result = [] + for p in list(reversed(zapi.getParents(self.context))) + [self.context]: + if not INode.providedBy(p) or p.nodeType != 'menu': + continue + ann = IAnnotations(p) + propDefs = ann.get(configurator.ANNOTATION_KEY, {}) + if propDefs: + result.extend([self.setupViewProperty(prop, propDef) + for prop, propDef in propDefs.items() if propDef]) + return result + + diff --git a/helpers.txt b/helpers.txt index 2a2dfc5..7b9cce4 100755 --- a/helpers.txt +++ b/helpers.txt @@ -13,6 +13,7 @@ Let's first do some basic imports >>> from zope.app.testing.setup import placefulSetUp, placefulTearDown >>> site = placefulSetUp(True) + >>> from zope import interface, component >>> from zope.app import zapi >>> from zope.app.tests import ztapi >>> from zope.interface import Interface @@ -32,6 +33,10 @@ and setup a simple loops site with its manager objects, >>> loopsRoot['resources'] = ResourceManager() >>> resources = loopsRoot['resources'] + >>> from loops.view import ViewManager, Node + >>> loopsRoot['views'] = ViewManager() + >>> views = loopsRoot['views'] + some concepts, >>> cc1 = Concept(u'Zope') @@ -44,7 +49,7 @@ some concepts, >>> cc2.title u'Zope 3' -and resources: +resources, >>> doc1 = Document(u'Zope Info') >>> resources['doc1'] = doc1 @@ -54,6 +59,16 @@ and resources: >>> img1 = MediaAsset(u'An Image') >>> resources['img1'] = img1 +and nodes (in view space): + + >>> m1 = Node(u'Home') + >>> views['m1'] = m1 + >>> m1.nodeType = 'menu' + + >>> m1p1 = Node(u'Page') + >>> m1['p1'] = m1p1 + >>> m1p1.nodeType = 'page' + Finally, we also need a relation registry: >>> from cybertools.relation.interfaces import IRelationRegistry @@ -229,3 +244,55 @@ i.e. the 'topic' concept, via an adapter: >>> cc1Adapter = cc1_type.typeInterface(cc1) >>> ITopic.providedBy(cc1Adapter) True + + +Controlling presentation using view properties +---------------------------------------------- + + >>> from zope.app.annotation.interfaces import IAttributeAnnotatable, IAnnotations + >>> from zope.app.annotation.attribute import AttributeAnnotations + >>> from loops.interfaces import INode + +First we have to make sure we can use attribute annotations with our nodes, +and we also have to register an IViewConfigurator adapter for them: + + >>> component.provideAdapter(AttributeAnnotations, (INode,), IAnnotations) + + >>> from cybertools.browser.configurator import IViewConfigurator + >>> from loops.browser.node import NodeViewConfigurator + >>> from zope.publisher.interfaces.browser import IBrowserRequest + >>> component.provideAdapter(NodeViewConfigurator, (INode, IBrowserRequest), + ... IViewConfigurator) + +Now we are ready to set up a view on our page node: + + >>> from loops.browser.node import NodeView + >>> request = TestRequest() + >>> view = NodeView(m1p1, request) + +The elements responsible for presentation are controlled by a controller +object: + + >>> from cybertools.browser.controller import Controller + >>> controller = Controller(view, request) + >>> getattr(controller, 'skinName', None) is None + True + +There is no `skinName` setting in the controller as we did not set any. +The configurator (IViewConfigurator adapter, see above) takes the +view properties from the attribute annotations. We set these properties +using an adapter to the config schema; the configurator will only use +settings on menu nodes (possibly above the node to be viewed in the +browser). + + >>> from loops.interfaces import IViewConfiguratorSchema + >>> from loops.browser.node import ViewPropertiesConfigurator + >>> component.provideAdapter(ViewPropertiesConfigurator, (INode,), + ... IViewConfiguratorSchema) + + >>> pageConfigurator = IViewConfiguratorSchema(m1) + >>> pageConfigurator.skinName = 'SuperSkin' + + >>> controller = Controller(view, request) + >>> controller.skinName.value + 'SuperSkin' diff --git a/interfaces.py b/interfaces.py index c9b8029..da05358 100644 --- a/interfaces.py +++ b/interfaces.py @@ -497,3 +497,14 @@ class ITypeConcept(Interface): source="loops.TypeInterfaceSource", required=False) + +# view configurator stuff + +class IViewConfiguratorSchema(Interface): + + skinName = schema.TextLine( + title=_(u'Skin Name'), + description=_(u'Name of the skin to use for this part of the site'), + default=u'', + required=False) +