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)
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
--------------
@ -605,8 +593,8 @@ to the bottom, and to the top.
['m111', 'm114', 'm112', 'm113']
End-user Forms
==============
End-user Forms and Special Views
================================
The browser.form and related modules provide additional support for forms
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)
u'a_title'
Editing an object
Editing an Object
-----------------
>>> from loops.browser.form import EditObjectForm, EditObject
@ -685,6 +673,40 @@ Editing an object
>>> resources['test_note'].title
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
=============

View file

@ -23,10 +23,11 @@ $Id$
"""
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.cachedescriptors.property import Lazy
from zope.dottedname.resolve import resolve
from zope.dublincore.interfaces import IZopeDublinCore
from zope.formlib import form
from zope.formlib.form import FormFields
from zope.formlib.namedtemplate import NamedTemplate
@ -38,6 +39,7 @@ from zope import schema
from zope.schema.vocabulary import SimpleTerm
from zope.security import canAccess, canWrite
from zope.security.proxy import removeSecurityProxy
from zope.traversing.browser import absoluteURL
from cybertools.browser.view import GenericView
from cybertools.relation.interfaces import IRelationRegistry
@ -78,7 +80,7 @@ class EditForm(form.EditForm):
def deleteObjectAction(self):
return None # better not to show the edit button at the moment
parent = zapi.getParent(self.context)
parentUrl = zapi.absoluteURL(parent, self.request)
parentUrl = absoluteURL(parent, self.request)
return parentUrl + '/contents.html'
@ -93,7 +95,7 @@ class BaseView(GenericView):
def setSkin(self, skinName):
skin = None
if skinName and IView.providedBy(self.context):
skin = zapi.queryUtility(IBrowserSkinType, skinName)
skin = component.queryUtility(IBrowserSkinType, skinName)
if skin:
applySkin(self.request, skin)
self.skin = skin
@ -112,7 +114,7 @@ class BaseView(GenericView):
@Lazy
def url(self):
return zapi.absoluteURL(self.context, self.request)
return absoluteURL(self.context, self.request)
@Lazy
def view(self):
@ -164,7 +166,7 @@ class BaseView(GenericView):
def typeUrl(self):
provider = self.typeProvider
if provider is not None:
return zapi.absoluteURL(provider, self.request)
return absoluteURL(provider, self.request)
return None
def viewIterator(self, objs):
@ -206,6 +208,12 @@ class BaseView(GenericView):
def editable(self):
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'):
if self.editable:
return "openEditWindow('%s/@@%s')" % (self.url, viewName)
@ -234,6 +242,20 @@ class BaseView(GenericView):
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):
""" Provide the ITerms interface, e.g. for usage in selection
lists.

View file

@ -557,6 +557,14 @@
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) -->
<page
@ -653,7 +661,7 @@
<zope:view factory="loops.view.NodeTraverser"
for="loops.interfaces.INode"
type="zope.publisher.interfaces.browser.IBrowserRequest"
type="zope.publisher.interfaces.http.IHTTPRequest"
provides="zope.publisher.interfaces.browser.IBrowserPublisher"
allowed_interface="zope.publisher.interfaces.browser.IBrowserPublisher"
permission="zope.Public" />

View file

@ -37,6 +37,7 @@ from zope.lifecycleevent import ObjectCreatedEvent, ObjectModifiedEvent
from zope.lifecycleevent import Attributes
from zope.formlib.form import Form, FormFields
from zope.formlib.namedtemplate import NamedTemplate
from zope.app.pagetemplate import ViewPageTemplateFile
from zope.proxy import removeAllProxies
from zope.security import canAccess, canWrite
from zope.security.proxy import removeSecurityProxy
@ -45,6 +46,7 @@ from cybertools.ajax import innerHtml
from cybertools.browser import configurator
from cybertools.browser.view import GenericView
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 IViewConfiguratorSchema
from loops.resource import MediaAsset
@ -54,6 +56,9 @@ from loops.browser.common import BaseView
from loops.browser.concept import ConceptView
node_macros = ViewPageTemplateFile('node_macros.pt')
class NodeView(BaseView):
_itemNum = 0
@ -171,7 +176,8 @@ class NodeView(BaseView):
# TODO: replace by something like: return self.target.macroName
target = self.targetObject
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'
if IConcept.providedBy(target):
return 'conceptbody'
@ -183,6 +189,8 @@ class NodeView(BaseView):
def editable(self):
return canWrite(self.context, 'body')
# menu stuff
@Lazy
def menuObject(self):
return self.context.getMenu()
@ -244,6 +252,8 @@ class NodeView(BaseView):
def active(self, item):
return item.context == self.context or item.context in self.parents
# virtual target support
def targetDefaultView(self):
target = self.virtualTargetObject
if target is not None:
@ -297,6 +307,14 @@ class NodeView(BaseView):
if target is not None:
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
def hasEditableTarget(self):
return IResource.providedBy(self.virtualTargetObject)
@ -313,6 +331,20 @@ class NodeView(BaseView):
cm.register('js-execute', jsCall, jsCall=jsCall)
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):
self.registerDojo()
cm = self.controller.macros

View file

@ -243,6 +243,15 @@
<!-- 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">
<a target="zmi"
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 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.node import NodeView
from loops.browser.node import NodeView, node_macros
from loops.interfaces import ITypeConcept
from loops.browser.util import html_quote
@ -113,16 +113,30 @@ class ResourceView(BaseView):
return self
def show(self):
""" show means: "download"..."""
context = self.context
ti = IType(context).typeInterface
if ti is not None:
context = ti(context)
data = context.data
response = self.request.response
response.setHeader('Content-Type', context.contentType)
response.setHeader('Content-Length', len(data))
if not context.contentType.startswith('image/'):
response.setHeader('Content-Disposition',
'attachment; filename=%s' % zapi.getName(context))
'attachment; filename=%s' % zapi.getName(self.context))
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):
for r in self.context.getConceptRelations():
yield ConceptRelationView(r, self.request)

View file

@ -39,10 +39,19 @@
<metal:block define-macro="download">
<div tal:attributes="ondblclick python: item.openEditWindow('edit.html')">
<h3 tal:content="item/title">Title</h3>
<a href="#"
tal:attributes="href string:${view/url}/.target${view/targetId}/view">
Download '<span tal:replace="item/title" />'
</a>
<p>
<a href="#"
tal:attributes="href string:${view/url}/.target${view/targetId}/view">
Download
</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>
</metal:block>

View file

@ -286,8 +286,15 @@ class DocumentWriteFileAdapter(object):
def write(self, data):
# TODO: use typeInterface...
ITextDocument(self.context).data = unicode(data.replace('\r', ''), 'UTF-8')
notify(ObjectModifiedEvent(self.context, Attributes(IDocument, 'data')))
ti = IType(self.context).typeInterface
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):

14
view.py
View file

@ -202,9 +202,15 @@ class NodeTraverser(ItemTraverser):
else:
target = self.context.target
if target is not None:
viewAnnotations = request.annotations.get('loops.view', {})
viewAnnotations['target'] = target
request.annotations['loops.view'] = viewAnnotations
return self.context
# 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
return self.context
return super(NodeTraverser, self).publishTraverse(request, name)