referencing targets from views/nodes on the UI: now fine with intIds; now also with correct access to images and files in the resource space; main control via browser views that now provide a macro for viewing
git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@1126 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
parent
f15f2e94e3
commit
d687e95a8f
11 changed files with 159 additions and 138 deletions
25
README.txt
25
README.txt
|
@ -443,13 +443,13 @@ Node Views
|
|||
>>> view = NodeView(m11, TestRequest())
|
||||
|
||||
>>> page = view.page
|
||||
>>> items = page.textItems()
|
||||
>>> items = page.textItems
|
||||
>>> for item in items:
|
||||
... print item.url, item.editable
|
||||
http://127.0.0.1/loops/views/m1/m11/m112 False
|
||||
|
||||
>>> menu = view.menu
|
||||
>>> items = menu.menuItems()
|
||||
>>> items = menu.menuItems
|
||||
>>> for item in items:
|
||||
... print item.url, view.selected(item)
|
||||
http://127.0.0.1/loops/views/m1/m11 True
|
||||
|
@ -458,9 +458,17 @@ A Node and its Target
|
|||
---------------------
|
||||
|
||||
When configuring a node you may specify what you want to do with respect
|
||||
to the node's target: associate an existing one or create a new one.
|
||||
to the node's target: associate an existing one or create a new one. When
|
||||
accessing a target via a node view it is usually wrapped in a corresponding
|
||||
view; these views we have to provide as multi-adapters:
|
||||
|
||||
>>> from loops.browser.node import ConfigureView
|
||||
>>> from loops.browser.resource import DocumentView, MediaAssetView
|
||||
>>> ztapi.provideAdapter(IDocument, Interface, DocumentView,
|
||||
... with=(IBrowserRequest,))
|
||||
>>> ztapi.provideAdapter(IMediaAsset, Interface, MediaAssetView,
|
||||
... with=(IBrowserRequest,))
|
||||
|
||||
>>> form = {'action': 'create', 'create.title': 'New Resource',
|
||||
... 'create.type': 'loops.resource.MediaAsset',}
|
||||
>>> view = ConfigureView(m111, TestRequest(form = form))
|
||||
|
@ -498,11 +506,6 @@ 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.app.publisher.interfaces.browser import IBrowserView
|
||||
>>> from loops.browser.resource import DocumentView
|
||||
>>> ztapi.provideAdapter(IDocument, Interface, DocumentView,
|
||||
... with=(IBrowserRequest,))
|
||||
|
||||
>>> from zope.component.interfaces import IFactory
|
||||
>>> from zope.app.renderer import rest
|
||||
>>> ztapi.provideUtility(IFactory, rest.ReStructuredTextSourceFactory,
|
||||
|
@ -513,13 +516,13 @@ and of a lot of other stuff needed for the rendering machine.
|
|||
|
||||
>>> m112.target = doc1
|
||||
>>> view = NodeView(m112, TestRequest())
|
||||
>>> view.renderTargetBody()
|
||||
>>> view.renderTarget()
|
||||
u''
|
||||
>>> doc1.data = u'Test data\n\nAnother paragraph'
|
||||
>>> view.renderTargetBody()
|
||||
>>> view.renderTarget()
|
||||
u'Test data\n\nAnother paragraph'
|
||||
>>> doc1.contentType = 'text/restructured'
|
||||
>>> view.renderTargetBody()
|
||||
>>> view.renderTarget()
|
||||
u'<p>Test data</p>\n<p>Another paragraph</p>\n'
|
||||
|
||||
It is possible to edit a target's attributes directly in an
|
||||
|
|
|
@ -25,6 +25,7 @@ $Id$
|
|||
from zope.app import zapi
|
||||
from zope.app.dublincore.interfaces import ICMFDublinCore
|
||||
from zope.app.form.browser.interfaces import ITerms
|
||||
from zope.app.intid.interfaces import IIntIds
|
||||
from zope.cachedescriptors.property import Lazy
|
||||
from zope.interface import implements
|
||||
from zope.security.proxy import removeSecurityProxy
|
||||
|
@ -86,6 +87,10 @@ class BaseView(object):
|
|||
for o in objs:
|
||||
yield BaseView(o, request)
|
||||
|
||||
@Lazy
|
||||
def uniqueId(self):
|
||||
return zapi.getUtility(IIntIds).getId(self.context)
|
||||
|
||||
|
||||
class LoopsTerms(object):
|
||||
""" Provide the ITerms interface, e.g. for usage in selection
|
||||
|
|
|
@ -158,6 +158,14 @@
|
|||
name="concept.html"
|
||||
/>
|
||||
|
||||
<zope:adapter
|
||||
for="loops.interfaces.IConcept
|
||||
zope.publisher.interfaces.browser.IBrowserRequest"
|
||||
provides="zope.interface.Interface"
|
||||
factory="loops.browser.concept.ConceptView"
|
||||
permission="zope.View"
|
||||
/>
|
||||
|
||||
<!-- resource manager -->
|
||||
|
||||
<addform
|
||||
|
@ -199,15 +207,6 @@
|
|||
|
||||
<!-- document -->
|
||||
|
||||
<!--<zope:view
|
||||
type="zope.publisher.interfaces.browser.IBrowserRequest"
|
||||
for="zope.schema.interfaces.IBytes"
|
||||
provides="zope.app.form.interfaces.IInputWidget"
|
||||
factory=".resource.DataWidget"
|
||||
permission="zope.Public"
|
||||
name="loops.resource.DataWidget"
|
||||
/>-->
|
||||
|
||||
<addform
|
||||
label="Add Document"
|
||||
name="AddLoopsDocument.html"
|
||||
|
@ -278,6 +277,14 @@
|
|||
filter="nothing"
|
||||
/>
|
||||
|
||||
<zope:adapter
|
||||
for="loops.interfaces.IMediaAsset
|
||||
zope.publisher.interfaces.browser.IBrowserRequest"
|
||||
provides="zope.interface.Interface"
|
||||
factory="loops.browser.resource.MediaAssetView"
|
||||
permission="zope.View"
|
||||
/>
|
||||
|
||||
<!-- view manager -->
|
||||
|
||||
<addform
|
||||
|
@ -423,7 +430,7 @@
|
|||
label="Edit Concept"
|
||||
name="edit_target.html"
|
||||
schema="loops.interfaces.IConcept"
|
||||
fields="title"
|
||||
fields="title conceptType"
|
||||
for="loops.interfaces.IConceptView"
|
||||
template="edit.pt"
|
||||
permission="zope.ManageContent"
|
||||
|
@ -444,11 +451,13 @@
|
|||
name="node.html"
|
||||
/>
|
||||
|
||||
<!-- render file or image assigned to a node as target -->
|
||||
|
||||
<page
|
||||
name="target"
|
||||
name="view"
|
||||
for="loops.interfaces.INode"
|
||||
class=".node.NodeView"
|
||||
attribute="renderTarget"
|
||||
attribute="targetDefaultView"
|
||||
permission="zope.View"
|
||||
/>
|
||||
|
||||
|
|
|
@ -23,17 +23,12 @@
|
|||
|
||||
|
||||
<metal:body fill-slot="body">
|
||||
<tal:content define="item view/page;
|
||||
<tal:content define="item view/item;
|
||||
level level|python: 1;
|
||||
target request/annotations/loops.view/target | nothing">
|
||||
<tal:content condition="not:target">
|
||||
<metal:block use-macro="views/node_macros/content" />
|
||||
macro item/macro">
|
||||
<tal:content>
|
||||
<metal:block use-macro="macro" />
|
||||
</tal:content>
|
||||
<tal:target condition="target">
|
||||
<div>
|
||||
Here comes the real target... <span tal:replace="target/title" />
|
||||
</div>
|
||||
</tal:target>
|
||||
</tal:content>
|
||||
</metal:body>
|
||||
|
||||
|
|
|
@ -27,6 +27,8 @@ from zope.app import zapi
|
|||
from zope.app.catalog.interfaces import ICatalog
|
||||
from zope.app.container.browser.contents import JustContents
|
||||
from zope.app.event.objectevent import ObjectCreatedEvent
|
||||
from zope.app.pagetemplate import ViewPageTemplateFile
|
||||
from zope.app.intid.interfaces import IIntIds
|
||||
from zope.dottedname.resolve import resolve
|
||||
from zope.event import notify
|
||||
from zope.proxy import removeAllProxies
|
||||
|
@ -43,6 +45,28 @@ from loops.browser.concept import ConceptView
|
|||
|
||||
class NodeView(BaseView):
|
||||
|
||||
template = ViewPageTemplateFile('node_macros.pt')
|
||||
macro = template.macros['content']
|
||||
|
||||
@Lazy
|
||||
def item(self):
|
||||
target = self.request.annotations.get('loops.view', {}).get('target')
|
||||
if target is not None:
|
||||
# .target.... traversal magic
|
||||
return zapi.getMultiAdapter((target, self.request))
|
||||
return self.page
|
||||
|
||||
@Lazy
|
||||
def page(self):
|
||||
page = self.context.getPage()
|
||||
return page is not None and NodeView(page, self.request) or None
|
||||
|
||||
@Lazy
|
||||
def textItems(self):
|
||||
return [NodeView(child, self.request)
|
||||
for child in self.context.getTextItems()]
|
||||
|
||||
|
||||
@Lazy
|
||||
def nodeType(self):
|
||||
return self.context.nodeType
|
||||
|
@ -66,33 +90,11 @@ class NodeView(BaseView):
|
|||
def target(self):
|
||||
obj = self.targetObject
|
||||
if obj is not None:
|
||||
if IConcept.providedBy(obj):
|
||||
return ConceptView(obj, self.request)
|
||||
return BaseView(obj, self.request)
|
||||
return zapi.getMultiAdapter((obj, self.request))
|
||||
|
||||
def renderTarget(self):
|
||||
target = self.targetObject
|
||||
if target is not None:
|
||||
targetView = zapi.getMultiAdapter((target, self.request),
|
||||
name=zapi.getDefaultViewName(target, self.request))
|
||||
return targetView()
|
||||
return u''
|
||||
|
||||
def renderTargetBody(self):
|
||||
target = self.targetObject
|
||||
if target is not None:
|
||||
targetView = zapi.getMultiAdapter((target, self.request))
|
||||
return targetView.render()
|
||||
return u''
|
||||
|
||||
@Lazy
|
||||
def page(self):
|
||||
page = self.context.getPage()
|
||||
return page is not None and NodeView(page, self.request) or None
|
||||
|
||||
def textItems(self):
|
||||
for child in self.context.getTextItems():
|
||||
yield NodeView(child, self.request)
|
||||
target = self.target
|
||||
return target is not None and target.render() or u''
|
||||
|
||||
@Lazy
|
||||
def body(self):
|
||||
|
@ -103,7 +105,7 @@ class NodeView(BaseView):
|
|||
target = self.targetObject
|
||||
if target is None or IDocument.providedBy(target):
|
||||
return 'textbody'
|
||||
if IConcept.providedBy(target): # TODO...
|
||||
if IConcept.providedBy(target):
|
||||
return 'conceptbody'
|
||||
if IMediaAsset.providedBy(target) and target.contentType.startswith('image/'):
|
||||
return 'imagebody'
|
||||
|
@ -118,22 +120,34 @@ class NodeView(BaseView):
|
|||
menu = self.context.getMenu()
|
||||
return menu is not None and NodeView(menu, self.request) or None
|
||||
|
||||
@Lazy
|
||||
def menuItems(self):
|
||||
for child in self.context.getMenuItems():
|
||||
yield NodeView(child, self.request)
|
||||
return [NodeView(child, self.request)
|
||||
for child in self.context.getMenuItems()]
|
||||
|
||||
def selected(self, item):
|
||||
return item.context == self.context
|
||||
|
||||
# view @@target - probably obsolete, replace by view.NodeTraverser
|
||||
def renderTarget(self):
|
||||
target = self.target
|
||||
if item.context == self.context:
|
||||
return True
|
||||
if item.context in zapi.getParents(self.context) and not item.menuItems:
|
||||
return True
|
||||
return False
|
||||
|
||||
def targetDefaultView(self):
|
||||
target = self.request.annotations.get('loops.view', {}).get('target')
|
||||
if target is None:
|
||||
target = self.targetObject
|
||||
if target is not None:
|
||||
targetView = zapi.getMultiAdapter((target, self.request),
|
||||
name=zapi.getDefaultViewName(target, self.request))
|
||||
return targetView()
|
||||
return u''
|
||||
|
||||
def targetId(self):
|
||||
target = self.request.annotations.get('loops.view', {}).get('target')
|
||||
if target is None:
|
||||
target = self.targetObject
|
||||
if target is not None:
|
||||
return zapi.getUtility(IIntIds).getId(target)
|
||||
|
||||
class ConfigureView(NodeView):
|
||||
""" An editing view for configuring a node, optionally creating
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
tal:condition="target"
|
||||
tal:attributes="class string:content-$level;
|
||||
ondblclick python: item.editable and onclick or ''"
|
||||
tal:content="structure item/renderTargetBody">
|
||||
tal:content="structure item/renderTarget">
|
||||
The body
|
||||
</div>
|
||||
</tal:body>
|
||||
|
@ -45,8 +45,7 @@
|
|||
</div>
|
||||
<div tal:repeat="resource target/resources">
|
||||
<a href="#"
|
||||
tal:attributes="href
|
||||
string:${item/url}/.target${repeat/resource/number}/@@node.html"
|
||||
tal:attributes="href string:${item/url}/.target${resource/uniqueId}"
|
||||
tal:content="resource/title">Resource Title</a>
|
||||
</div>
|
||||
</tal:body>
|
||||
|
@ -60,7 +59,7 @@
|
|||
tal:attributes="class string:content-$level;
|
||||
ondblclick python: item.editable and onclick or ''">
|
||||
<a href="#"
|
||||
tal:attributes="href string:${item/url}/@@target"
|
||||
tal:attributes="href string:${item/url}/.target/view"
|
||||
tal:content="structure body">The body</a>
|
||||
</div>
|
||||
</tal:body>
|
||||
|
@ -74,7 +73,7 @@
|
|||
tal:attributes="class string:content-$level;
|
||||
ondblclick python: item.editable and onclick or ''">
|
||||
<img src="target"
|
||||
tal:attributes="src string:${item/url}/@@target" />
|
||||
tal:attributes="src string:${item/url}/.target/view" />
|
||||
</div>
|
||||
<div class="content-1"
|
||||
tal:condition="body"
|
||||
|
|
|
@ -26,6 +26,7 @@ from zope.cachedescriptors.property import Lazy
|
|||
from zope.app import zapi
|
||||
from zope.app.catalog.interfaces import ICatalog
|
||||
from zope.app.dublincore.interfaces import ICMFDublinCore
|
||||
from zope.app.pagetemplate import ViewPageTemplateFile
|
||||
from zope.proxy import removeAllProxies
|
||||
from zope.security import canAccess, canWrite
|
||||
from zope.security.proxy import removeSecurityProxy
|
||||
|
@ -46,6 +47,8 @@ renderingFactories = {
|
|||
|
||||
class ResourceView(BaseView):
|
||||
|
||||
template = ViewPageTemplateFile('resource_macros.pt')
|
||||
|
||||
def concepts(self):
|
||||
for r in self.context.getConceptRelations():
|
||||
yield ConceptRelationView(r, self.request)
|
||||
|
@ -112,6 +115,8 @@ class ResourceConfigureView(ResourceView, ConceptConfigureView):
|
|||
|
||||
class DocumentView(ResourceView):
|
||||
|
||||
macro = ResourceView.template.macros['render']
|
||||
|
||||
def render(self):
|
||||
""" Return the rendered content (data) of the context object.
|
||||
"""
|
||||
|
@ -124,3 +129,11 @@ class DocumentView(ResourceView):
|
|||
return view.render()
|
||||
|
||||
|
||||
class MediaAssetView(ResourceView):
|
||||
|
||||
@property
|
||||
def macro(self):
|
||||
if 'image/' in self.context.contentType:
|
||||
return self.template.macros['image']
|
||||
else:
|
||||
return self.template.macros['download']
|
||||
|
|
27
browser/resource_macros.pt
Normal file
27
browser/resource_macros.pt
Normal file
|
@ -0,0 +1,27 @@
|
|||
<metal:block define-macro="render">
|
||||
<div>
|
||||
Here comes a document...
|
||||
<span tal:replace="structure item/render" />
|
||||
</div>
|
||||
</metal:block>
|
||||
|
||||
|
||||
<metal:block define-macro="image">
|
||||
<div>
|
||||
Here comes an image...
|
||||
<img src="#"
|
||||
tal:attributes="src string:${view/url}/.target${view/targetId}/view" />
|
||||
</div>
|
||||
</metal:block>
|
||||
|
||||
|
||||
<metal:block define-macro="download">
|
||||
<div>
|
||||
Here comes a file...
|
||||
<a href="#"
|
||||
tal:attributes="href string:${view/url}/.target${view/targetId}/view">
|
||||
Download '<span tal:replace="item/title" />'
|
||||
</a>
|
||||
</div>
|
||||
</metal:block>
|
||||
|
|
@ -53,7 +53,7 @@ class IPotentialTarget(Interface):
|
|||
|
||||
proxyInterface = Attribute('An interface allowing an object to be '
|
||||
'used as a target for a view/node (and '
|
||||
'typically specifying the corresponding schema')
|
||||
'typically specifying the corresponding schema)')
|
||||
|
||||
|
||||
# concept interfaces
|
||||
|
@ -399,39 +399,8 @@ class INodeContained(Interface):
|
|||
containers(INode, IViewManager)
|
||||
|
||||
|
||||
# schemas to be used by forms on view/node objects
|
||||
|
||||
class ITargetProperties(Interface):
|
||||
""" Fields used for specifying a view's or node's target.
|
||||
"""
|
||||
|
||||
targetType = schema.Choice(
|
||||
title=_(u'Target Type'),
|
||||
description=_(u'Type of the target'),
|
||||
values=('loops.resource.Document', 'loops.resource.MediaAsset',
|
||||
'loops.concept.Concept'),
|
||||
default=None,
|
||||
required=False)
|
||||
|
||||
targetUri = schema.TextLine(
|
||||
title=_(u'Target URI'),
|
||||
description=_(u'An URI being a unique reference to the target'),
|
||||
required=False)
|
||||
|
||||
|
||||
class INodeConfigSchema(INode, ITargetProperties):
|
||||
""" All fields that may be shown in the node config form.
|
||||
"""
|
||||
|
||||
createTarget = schema.Bool(
|
||||
title=_(u'Create Target'),
|
||||
description=_(u'Should a new target object be created?'),
|
||||
required=False)
|
||||
|
||||
|
||||
# the loops top-level container
|
||||
|
||||
#class ILoops(ILoopsObject, IFolder):
|
||||
class ILoops(ILoopsObject):
|
||||
""" The top-level object of a loops site.
|
||||
"""
|
||||
|
@ -476,20 +445,6 @@ class IConceptRelation(IRelation):
|
|||
"""
|
||||
|
||||
|
||||
# type and type manager interfaces - probably obsolete
|
||||
|
||||
# class ILoopsType(IType):
|
||||
# """ Each loops object is of a certain type providing this interface.
|
||||
# Usually implemented as an adapter.
|
||||
# """
|
||||
|
||||
|
||||
# class ILoopsTypeManager(ITypeManager):
|
||||
# """ The loops type manager, probably implemented by an adapter to
|
||||
# the loops root object or the loops root object itself.
|
||||
# """
|
||||
|
||||
|
||||
# interfaces for catalog indexes
|
||||
|
||||
class IIndexAttributes(Interface):
|
||||
|
|
37
target.py
37
target.py
|
@ -42,19 +42,32 @@ _ = MessageFactory('loops')
|
|||
|
||||
# proxies for accessing target objects from views/nodes
|
||||
|
||||
class ConceptProxy(object):
|
||||
|
||||
implements(IConcept)
|
||||
adapts(IConceptView)
|
||||
class TargetProxy(object):
|
||||
|
||||
def __init__(self, context):
|
||||
#self.context = context
|
||||
self.context = removeSecurityProxy(context)
|
||||
|
||||
def getTitle(self): return self.target.title
|
||||
@Lazy
|
||||
def target(self):
|
||||
return self.context.target
|
||||
|
||||
def getTitle(self):
|
||||
return self.target.title
|
||||
def setTitle(self, title): self.target.title = title
|
||||
title = property(getTitle, setTitle)
|
||||
|
||||
|
||||
class ConceptProxy(TargetProxy):
|
||||
|
||||
implements(IConcept)
|
||||
adapts(IConceptView)
|
||||
|
||||
def getConceptType(self): return self.target.conceptType
|
||||
def setConceptType(self, conceptType): self.target.conceptType = conceptType
|
||||
conceptType = property(getConceptType, setConceptType)
|
||||
|
||||
def getChildren(self, predicates=None):
|
||||
return self.target.getChildren(predicates)
|
||||
|
||||
|
@ -65,27 +78,13 @@ class ConceptProxy(object):
|
|||
return self.target.getResources(predicates)
|
||||
|
||||
|
||||
class ResourceProxy(object):
|
||||
|
||||
adapts(IView)
|
||||
|
||||
def __init__(self, context):
|
||||
#self.context = context
|
||||
self.context = removeSecurityProxy(context)
|
||||
|
||||
def getTitle(self): return self.target.title
|
||||
def setTitle(self, title): self.target.title = title
|
||||
title = property(getTitle, setTitle)
|
||||
class ResourceProxy(TargetProxy):
|
||||
|
||||
def setContentType(self, contentType):
|
||||
self.target.contentType = contentType
|
||||
def getContentType(self): return self.target.contentType
|
||||
contentType = property(getContentType, setContentType)
|
||||
|
||||
@Lazy
|
||||
def target(self):
|
||||
return self.context.target
|
||||
|
||||
|
||||
class DocumentProxy(ResourceProxy):
|
||||
|
||||
|
|
8
view.py
8
view.py
|
@ -28,6 +28,7 @@ from zope.app.container.contained import Contained
|
|||
from zope.app.container.ordered import OrderedContainer
|
||||
from zope.app.container.traversal import ContainerTraverser, ItemTraverser
|
||||
from zope.app.container.traversal import ContainerTraversable
|
||||
from zope.app.intid.interfaces import IIntIds
|
||||
from zope.cachedescriptors.property import Lazy, readproperty
|
||||
from zope.component import adapts
|
||||
from zope.interface import implements
|
||||
|
@ -38,7 +39,7 @@ from cybertools.relation import DyadicRelation
|
|||
from cybertools.relation.registry import getRelations
|
||||
from cybertools.relation.interfaces import IRelationRegistry, IRelatable
|
||||
|
||||
from interfaces import IView, INode, INodeConfigSchema
|
||||
from interfaces import IView, INode
|
||||
from interfaces import IViewManager, INodeContained
|
||||
from interfaces import ILoopsContained
|
||||
from interfaces import ITargetRelation
|
||||
|
@ -171,8 +172,9 @@ class NodeTraverser(ItemTraverser):
|
|||
if name.startswith('.target'):
|
||||
target = self.context.target
|
||||
if len(name) > len('.target') and IConcept.providedBy(target):
|
||||
idx = int(name[len('.target'):]) - 1
|
||||
target = target.getResources()[idx]
|
||||
idx = int(name[len('.target'):])
|
||||
target = zapi.getUtility(IIntIds).getObject(idx)
|
||||
#target = target.getResources()[idx]
|
||||
viewAnnotations = request.annotations.get('loops.view', {})
|
||||
viewAnnotations['target'] = target
|
||||
request.annotations['loops.view'] = viewAnnotations
|
||||
|
|
Loading…
Add table
Reference in a new issue