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:
parent
ab28635ff2
commit
24fb20d78a
9 changed files with 164 additions and 35 deletions
52
README.txt
52
README.txt
|
@ -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
|
||||
=============
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'"
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
11
resource.py
11
resource.py
|
@ -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
14
view.py
|
@ -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)
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue