diff --git a/README.txt b/README.txt index 3ed2701..cfcd434 100755 --- a/README.txt +++ b/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) + + >>> view = NodeView(m111, request) + >>> view.virtualTargetObject + + +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() + [] + >>> action = view.virtualTarget.getActions()[0] + >>> action.url + 'http://127.0.0.1/loops/views/m1/m11/m111/.target16' + Import/Export ============= diff --git a/browser/common.py b/browser/common.py index eae5999..215657a 100644 --- a/browser/common.py +++ b/browser/common.py @@ -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. diff --git a/browser/configure.zcml b/browser/configure.zcml index b7c477d..8b3f8f2 100644 --- a/browser/configure.zcml +++ b/browser/configure.zcml @@ -557,6 +557,14 @@ permission="zope.View" /> + + + diff --git a/browser/node.py b/browser/node.py index e56cf1d..8ad8b81 100644 --- a/browser/node.py +++ b/browser/node.py @@ -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 diff --git a/browser/node_macros.pt b/browser/node_macros.pt index 9591d33..c03ed7c 100644 --- a/browser/node_macros.pt +++ b/browser/node_macros.pt @@ -243,6 +243,15 @@ + + External Editor + + + diff --git a/resource.py b/resource.py index 4e90f98..5120a26 100644 --- a/resource.py +++ b/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): diff --git a/view.py b/view.py index a4c34b3..3f68c0c 100644 --- a/view.py +++ b/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)