provide external editor usage for files, e.g. word documents

git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@1589 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
helmutm 2007-02-19 13:00:20 +00:00
parent ab28635ff2
commit 24fb20d78a
9 changed files with 164 additions and 35 deletions

View file

@ -564,18 +564,6 @@ cybertools.relation package.)
>>> IMediaAssetView.providedBy(m111) >>> IMediaAssetView.providedBy(m111)
False False
Views Related to Virtual Targets
--------------------------------
From a node usually any object in the concept or resource space can
be accessed as a `virtual target`. This is done by putting ".targetNNN"
at the end of the URL, with NNN being the unique id of the concept
or resource.
>>> from loops.view import NodeTraverser
>>> from zope.publisher.interfaces.browser import IBrowserPublisher
>>> component.provideAdapter(NodeTraverser, provides=IBrowserPublisher)
Ordering Nodes Ordering Nodes
-------------- --------------
@ -605,8 +593,8 @@ to the bottom, and to the top.
['m111', 'm114', 'm112', 'm113'] ['m111', 'm114', 'm112', 'm113']
End-user Forms End-user Forms and Special Views
============== ================================
The browser.form and related modules provide additional support for forms The browser.form and related modules provide additional support for forms
that are shown in the end-user interface. that are shown in the end-user interface.
@ -670,7 +658,7 @@ and possibly critcal cases:
>>> nc.chooseName(u'A very very loooooong title', None) >>> nc.chooseName(u'A very very loooooong title', None)
u'a_title' u'a_title'
Editing an object Editing an Object
----------------- -----------------
>>> from loops.browser.form import EditObjectForm, EditObject >>> from loops.browser.form import EditObjectForm, EditObject
@ -685,6 +673,40 @@ Editing an object
>>> resources['test_note'].title >>> resources['test_note'].title
u'Test Note - changed' u'Test Note - changed'
Virtual Targets
---------------
From a node usually any object in the concept or resource space can
be accessed as a `virtual target`. This is done by putting ".targetNNN"
at the end of the URL, with NNN being the unique id of the concept
or resource.
>>> from loops.view import NodeTraverser
>>> magic = '.target' + util.getUidForObject(note)
>>> url = 'http://127.0.0.1/loops/views/m1/m11/m111/' + magic + '/@@node.html'
>>> #request = TestRequest(environ=dict(SERVER_URL=url))
>>> request = TestRequest()
>>> NodeTraverser(m111, request).publishTraverse(request, magic)
<loops.view.Node object ...>
>>> view = NodeView(m111, request)
>>> view.virtualTargetObject
<loops.resource.Resource object ...>
A virtual target may be edited in the same way like directly assigned targets,
see above, "Editing an Object". In addition, target objects may be viewed
and edited in special ways, depending on the target object's type.
In order to provide suitable links for viewing or editing a target you may
ask a view which view and edit actions it supports. We directly use the
target object's view here:
>>> view.virtualTarget.getActions()
[<loops.browser.common.Action object ...>]
>>> action = view.virtualTarget.getActions()[0]
>>> action.url
'http://127.0.0.1/loops/views/m1/m11/m111/.target16'
Import/Export Import/Export
============= =============

View file

@ -23,10 +23,11 @@ $Id$
""" """
from zope.app import zapi from zope.app import zapi
from zope.dublincore.interfaces import IZopeDublinCore from zope import component
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
from zope.dottedname.resolve import resolve from zope.dottedname.resolve import resolve
from zope.dublincore.interfaces import IZopeDublinCore
from zope.formlib import form from zope.formlib import form
from zope.formlib.form import FormFields from zope.formlib.form import FormFields
from zope.formlib.namedtemplate import NamedTemplate from zope.formlib.namedtemplate import NamedTemplate
@ -38,6 +39,7 @@ from zope import schema
from zope.schema.vocabulary import SimpleTerm from zope.schema.vocabulary import SimpleTerm
from zope.security import canAccess, canWrite from zope.security import canAccess, canWrite
from zope.security.proxy import removeSecurityProxy from zope.security.proxy import removeSecurityProxy
from zope.traversing.browser import absoluteURL
from cybertools.browser.view import GenericView from cybertools.browser.view import GenericView
from cybertools.relation.interfaces import IRelationRegistry from cybertools.relation.interfaces import IRelationRegistry
@ -78,7 +80,7 @@ class EditForm(form.EditForm):
def deleteObjectAction(self): def deleteObjectAction(self):
return None # better not to show the edit button at the moment return None # better not to show the edit button at the moment
parent = zapi.getParent(self.context) parent = zapi.getParent(self.context)
parentUrl = zapi.absoluteURL(parent, self.request) parentUrl = absoluteURL(parent, self.request)
return parentUrl + '/contents.html' return parentUrl + '/contents.html'
@ -93,7 +95,7 @@ class BaseView(GenericView):
def setSkin(self, skinName): def setSkin(self, skinName):
skin = None skin = None
if skinName and IView.providedBy(self.context): if skinName and IView.providedBy(self.context):
skin = zapi.queryUtility(IBrowserSkinType, skinName) skin = component.queryUtility(IBrowserSkinType, skinName)
if skin: if skin:
applySkin(self.request, skin) applySkin(self.request, skin)
self.skin = skin self.skin = skin
@ -112,7 +114,7 @@ class BaseView(GenericView):
@Lazy @Lazy
def url(self): def url(self):
return zapi.absoluteURL(self.context, self.request) return absoluteURL(self.context, self.request)
@Lazy @Lazy
def view(self): def view(self):
@ -164,7 +166,7 @@ class BaseView(GenericView):
def typeUrl(self): def typeUrl(self):
provider = self.typeProvider provider = self.typeProvider
if provider is not None: if provider is not None:
return zapi.absoluteURL(provider, self.request) return absoluteURL(provider, self.request)
return None return None
def viewIterator(self, objs): def viewIterator(self, objs):
@ -206,6 +208,12 @@ class BaseView(GenericView):
def editable(self): def editable(self):
return canWrite(self.context, 'title') return canWrite(self.context, 'title')
def getActions(self, category):
""" Return a list of actions that provide the view and edit actions
available for the context object.
"""
return []
def openEditWindow(self, viewName='edit.html'): def openEditWindow(self, viewName='edit.html'):
if self.editable: if self.editable:
return "openEditWindow('%s/@@%s')" % (self.url, viewName) return "openEditWindow('%s/@@%s')" % (self.url, viewName)
@ -234,6 +242,20 @@ class BaseView(GenericView):
cm.register('js', 'dojo.js', resourceName='ajax.dojo/dojo.js') cm.register('js', 'dojo.js', resourceName='ajax.dojo/dojo.js')
# actions
class Action(object):
def __init__(self, renderer, url, **kw):
self.renderer = renderer
self.url = url
self.__dict__.update(kw)
#for k in kw:
# setattr(self, k, kw[k])
# vocabulary stuff
class LoopsTerms(object): class LoopsTerms(object):
""" Provide the ITerms interface, e.g. for usage in selection """ Provide the ITerms interface, e.g. for usage in selection
lists. lists.

View file

@ -557,6 +557,14 @@
permission="zope.View" permission="zope.View"
/> />
<page
name="external_edit"
for="loops.interfaces.INode"
class="loops.browser.node.NodeView"
attribute="externalEdit"
permission="zope.ManageContent">
</page>
<!-- forms (end-user view) --> <!-- forms (end-user view) -->
<page <page
@ -653,7 +661,7 @@
<zope:view factory="loops.view.NodeTraverser" <zope:view factory="loops.view.NodeTraverser"
for="loops.interfaces.INode" for="loops.interfaces.INode"
type="zope.publisher.interfaces.browser.IBrowserRequest" type="zope.publisher.interfaces.http.IHTTPRequest"
provides="zope.publisher.interfaces.browser.IBrowserPublisher" provides="zope.publisher.interfaces.browser.IBrowserPublisher"
allowed_interface="zope.publisher.interfaces.browser.IBrowserPublisher" allowed_interface="zope.publisher.interfaces.browser.IBrowserPublisher"
permission="zope.Public" /> permission="zope.Public" />

View file

@ -37,6 +37,7 @@ from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent
from zope.lifecycleevent import Attributes from zope.lifecycleevent import Attributes
from zope.formlib.form import Form, FormFields from zope.formlib.form import Form, FormFields
from zope.formlib.namedtemplate import NamedTemplate from zope.formlib.namedtemplate import NamedTemplate
from zope.app.pagetemplate import ViewPageTemplateFile
from zope.proxy import removeAllProxies from zope.proxy import removeAllProxies
from zope.security import canAccess, canWrite from zope.security import canAccess, canWrite
from zope.security.proxy import removeSecurityProxy from zope.security.proxy import removeSecurityProxy
@ -45,6 +46,7 @@ from cybertools.ajax import innerHtml
from cybertools.browser import configurator from cybertools.browser import configurator
from cybertools.browser.view import GenericView from cybertools.browser.view import GenericView
from cybertools.typology.interfaces import IType, ITypeManager from cybertools.typology.interfaces import IType, ITypeManager
from cybertools.xedit.browser import ExternalEditorView
from loops.interfaces import IConcept, IResource, IDocument, IMediaAsset, INode from loops.interfaces import IConcept, IResource, IDocument, IMediaAsset, INode
from loops.interfaces import IViewConfiguratorSchema from loops.interfaces import IViewConfiguratorSchema
from loops.resource import MediaAsset from loops.resource import MediaAsset
@ -54,6 +56,9 @@ from loops.browser.common import BaseView
from loops.browser.concept import ConceptView from loops.browser.concept import ConceptView
node_macros = ViewPageTemplateFile('node_macros.pt')
class NodeView(BaseView): class NodeView(BaseView):
_itemNum = 0 _itemNum = 0
@ -171,7 +176,8 @@ class NodeView(BaseView):
# TODO: replace by something like: return self.target.macroName # TODO: replace by something like: return self.target.macroName
target = self.targetObject target = self.targetObject
if (target is None or IDocument.providedBy(target) if (target is None or IDocument.providedBy(target)
or (IResource.providedBy(target) and target.contentType.startswith('text/'))): or (IResource.providedBy(target) and
target.contentType.startswith('text/'))):
return 'textbody' return 'textbody'
if IConcept.providedBy(target): if IConcept.providedBy(target):
return 'conceptbody' return 'conceptbody'
@ -183,6 +189,8 @@ class NodeView(BaseView):
def editable(self): def editable(self):
return canWrite(self.context, 'body') return canWrite(self.context, 'body')
# menu stuff
@Lazy @Lazy
def menuObject(self): def menuObject(self):
return self.context.getMenu() return self.context.getMenu()
@ -244,6 +252,8 @@ class NodeView(BaseView):
def active(self, item): def active(self, item):
return item.context == self.context or item.context in self.parents return item.context == self.context or item.context in self.parents
# virtual target support
def targetDefaultView(self): def targetDefaultView(self):
target = self.virtualTargetObject target = self.virtualTargetObject
if target is not None: if target is not None:
@ -297,6 +307,14 @@ class NodeView(BaseView):
if target is not None: if target is not None:
return BaseView(target, self.request).url return BaseView(target, self.request).url
# target viewing and editing support
def getActions(self, category='object'):
#target = self.virtualTarget
#if target is not None:
# return target.getActions(category)
return [] # TODO: what about editing the node itself?
@Lazy @Lazy
def hasEditableTarget(self): def hasEditableTarget(self):
return IResource.providedBy(self.virtualTargetObject) return IResource.providedBy(self.virtualTargetObject)
@ -313,6 +331,20 @@ class NodeView(BaseView):
cm.register('js-execute', jsCall, jsCall=jsCall) cm.register('js-execute', jsCall, jsCall=jsCall)
return 'return inlineEdit("%s", "%s/inline_save")' % (id, self.virtualTargetUrl) return 'return inlineEdit("%s", "%s/inline_save")' % (id, self.virtualTargetUrl)
def externalEdit(self):
target = self.virtualTargetObject
if target is None:
target = self.context
url = self.url
else:
ti = IType(target).typeInterface
if ti is not None:
target = ti(target)
url = self.virtualTargetUrl
return ExternalEditorView(target, self.request).load(url=url)
# helper methods
def registerDojoDialog(self): def registerDojoDialog(self):
self.registerDojo() self.registerDojo()
cm = self.controller.macros cm = self.controller.macros

View file

@ -243,6 +243,15 @@
<!-- edit and other links --> <!-- edit and other links -->
<metal:xedit define-macro="external_edit"
tal:define="url action/url">
<a href="#" title="Edit with External Editor"
tal:attributes="href string:$url/external_edit"
><img src="edit.gif" alt="External Editor"
tal:attributes="src context/++resource++edit.gif" /></a>
</metal:xedit>
<metal:editlink define-macro="editlink"> <metal:editlink define-macro="editlink">
<a target="zmi" <a target="zmi"
tal:define="url string:${item/url}/@@edit.html'" tal:define="url string:${item/url}/@@edit.html'"

View file

@ -37,9 +37,9 @@ from zope.security.proxy import removeSecurityProxy
from cybertools.typology.interfaces import IType from cybertools.typology.interfaces import IType
from loops.interfaces import IBaseResource, IDocument, IMediaAsset from loops.interfaces import IBaseResource, IDocument, IMediaAsset
from loops.browser.common import EditForm, BaseView from loops.browser.common import EditForm, BaseView, Action
from loops.browser.concept import ConceptRelationView, ConceptConfigureView from loops.browser.concept import ConceptRelationView, ConceptConfigureView
from loops.browser.node import NodeView from loops.browser.node import NodeView, node_macros
from loops.interfaces import ITypeConcept from loops.interfaces import ITypeConcept
from loops.browser.util import html_quote from loops.browser.util import html_quote
@ -113,16 +113,30 @@ class ResourceView(BaseView):
return self return self
def show(self): def show(self):
""" show means: "download"..."""
context = self.context context = self.context
ti = IType(context).typeInterface
if ti is not None:
context = ti(context)
data = context.data data = context.data
response = self.request.response response = self.request.response
response.setHeader('Content-Type', context.contentType) response.setHeader('Content-Type', context.contentType)
response.setHeader('Content-Length', len(data)) response.setHeader('Content-Length', len(data))
if not context.contentType.startswith('image/'): if not context.contentType.startswith('image/'):
response.setHeader('Content-Disposition', response.setHeader('Content-Disposition',
'attachment; filename=%s' % zapi.getName(context)) 'attachment; filename=%s' % zapi.getName(self.context))
return data return data
def getActions(self, category='object'):
renderer = node_macros.macros['external_edit']
node = self.request.annotations.get('loops.view', {}).get('node')
if node is not None:
nodeView = NodeView(node, self.request)
url = nodeView.virtualTargetUrl
else:
url = self.url
return [Action(renderer, url)]
def concepts(self): def concepts(self):
for r in self.context.getConceptRelations(): for r in self.context.getConceptRelations():
yield ConceptRelationView(r, self.request) yield ConceptRelationView(r, self.request)

View file

@ -39,10 +39,19 @@
<metal:block define-macro="download"> <metal:block define-macro="download">
<div tal:attributes="ondblclick python: item.openEditWindow('edit.html')"> <div tal:attributes="ondblclick python: item.openEditWindow('edit.html')">
<h3 tal:content="item/title">Title</h3> <h3 tal:content="item/title">Title</h3>
<p>
<a href="#" <a href="#"
tal:attributes="href string:${view/url}/.target${view/targetId}/view"> tal:attributes="href string:${view/url}/.target${view/targetId}/view">
Download '<span tal:replace="item/title" />' Download
</a> </a>
<tal:xedit condition="view/xeditable"> |
<a href="#" title="Edit with External Editor"
tal:define="url string:${view/url}/.target${view/targetId}"
tal:attributes="href string:$url/external_edit">
Open for editing
</a>
</tal:xedit>
</p>
</div> </div>
</metal:block> </metal:block>

View file

@ -286,8 +286,15 @@ class DocumentWriteFileAdapter(object):
def write(self, data): def write(self, data):
# TODO: use typeInterface... # TODO: use typeInterface...
ITextDocument(self.context).data = unicode(data.replace('\r', ''), 'UTF-8') ti = IType(self.context).typeInterface
notify(ObjectModifiedEvent(self.context, Attributes(IDocument, 'data'))) context = ti is None and self.context or ti(self.context)
if ITextDocument.providedBy(context):
context.data = unicode(data.replace('\r', ''), 'UTF-8')
else:
# TODO: make use of tmpfile when using external files
context.data = data
#ITextDocument(self.context).data = unicode(data.replace('\r', ''), 'UTF-8')
notify(ObjectModifiedEvent(self.context, Attributes(IResource, 'data')))
class DocumentReadFileAdapter(object): class DocumentReadFileAdapter(object):

10
view.py
View file

@ -202,9 +202,15 @@ class NodeTraverser(ItemTraverser):
else: else:
target = self.context.target target = self.context.target
if target is not None: if target is not None:
viewAnnotations = request.annotations.get('loops.view', {}) # remember self.context in request
viewAnnotations = request.annotations.setdefault('loops.view', {})
viewAnnotations['node'] = self.context
if request.method == 'PUT':
# we have to use the target object directly
return target
else:
# we'll use the target object in the node's context
viewAnnotations['target'] = target viewAnnotations['target'] = target
request.annotations['loops.view'] = viewAnnotations
return self.context return self.context
return super(NodeTraverser, self).publishTraverse(request, name) return super(NodeTraverser, self).publishTraverse(request, name)