From a0948469d44e31a095df89454bf8fa60ce70ca4d Mon Sep 17 00:00:00 2001 From: helmutm Date: Sat, 25 Feb 2006 18:11:24 +0000 Subject: [PATCH] Work in progress: assign target to node using a search form git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@1097 fd906abe-77d9-0310-91a1-e0d9ade77398 --- README.txt | 115 ++++++------------------------ browser/common.py | 15 +++- browser/concept.py | 8 +++ browser/configure.zcml | 13 +++- browser/edit.pt | 3 +- browser/node.py | 140 +++++++++++++++++++++++-------------- browser/node_target.pt | 43 ++++++++++++ browser/target_macros.pt | 146 +++++++++++++++++++++++++++++++++++++++ configure.zcml | 3 +- target.py | 9 +++ 10 files changed, 346 insertions(+), 149 deletions(-) create mode 100644 browser/node_target.pt create mode 100644 browser/target_macros.pt diff --git a/README.txt b/README.txt index b162a1a..0f1c80b 100755 --- a/README.txt +++ b/README.txt @@ -390,113 +390,44 @@ Node Views ... print item.url, view.selected(item) http://127.0.0.1/loops/views/m1/m11 True -Node Configuration ------------------- +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. -These options are provided via the INodeConfigSchema that is provided -by a NodeConfigAdapter; in addition the attributes of the node (like the -title) may be changed via the NodeConfigAdapter. - - >>> from loops.interfaces import INodeConfigSchema - >>> from loops.view import NodeConfigAdapter - >>> ztapi.provideAdapter(INode, INodeConfigSchema, NodeConfigAdapter) - >>> nodeConfig = INodeConfigSchema(m111) - >>> nodeConfig.title = u'New title for m111' - >>> nodeConfig.title - u'New title for m111' - >>> m111.title - u'New title for m111' - >>> nodeConfig.target = doc1 - >>> m111.target is doc1 + >>> from loops.browser.node import ConfigureView + >>> form = {'action': 'create', 'create.title': 'New Resource', + ... 'create.type': 'loops.resource.MediaAsset',} + >>> view = ConfigureView(m111, TestRequest(form = form)) + >>> sorted((t.token, t.title) for t in view.targetTypes()) + [('loops.concept.Concept', u'Concept'), + ('loops.resource.Document', u'Document'), + ('loops.resource.MediaAsset', u'Media Asset')] + >>> view.update() True - >>> m111 in doc1.getClients() - True - -The targetUri and targetType fields are only relevant when creating -a new target object: - - >>> nodeConfig.targetUri - '' - >>> nodeConfig.targetType - 'loops.resource.Document' - -The node configuration form provides a target assignment field using -a vocabulary (source) for selecting the target. (In a future version this form -will be extended by a widget that lets you search for potential target -objects.) The source is basically a source list: - - >>> from loops.target import TargetSourceList - >>> source = TargetSourceList(m111) - >>> len(source) - 1 - >>> sorted([zapi.getName(s) for s in source]) - [u'doc1'] - -The form then uses a sort of browser view providing the ITerms interface -based on this source list: - - >>> terms = LoopsTerms(source, TestRequest()) - >>> term = terms.getTerm(doc1) - >>> term.token, term.title, term.value - ('.loops/resources/doc1', u'Zope Info', ) - - >>> term = terms.getTerm(cc1) - >>> term.token, term.title, term.value - ('.loops/concepts/cc1', u'cc1', ) - - >>> terms.getValue('.loops/concepts/cc1') is cc1 - True - -There is a special edit view class that can be used to configure a node -in a way that allows the creation of a target object on the fly. -(We here use the base class providing the method for this action; the real -application uses a subclass that does all the other stuff for form handling.) -When creating a new target object you may specify a uri that determines -the location of the new target object and its name. - - >>> from loops.browser.node import ConfigureBaseView - >>> view = ConfigureBaseView(INodeConfigSchema(m111), TestRequest()) - >>> view.checkCreateTarget() >>> sorted(resources.keys()) - [u'doc1'] - >>> form = {'field.createTarget': True, - ... 'field.targetUri': '.loops/resources/ma07', - ... 'field.targetType': 'loops.resource.MediaAsset'} - >>> view = ConfigureBaseView(m111, TestRequest(form=form)) - >>> m111.target = view.checkCreateTarget() - >>> sorted(resources.keys()) - [u'doc1', u'ma07'] - >>> isinstance(resources['ma07'], MediaAsset) - True - - >>> form = {'field.createTarget': True, - ... 'field.targetType': 'loops.resource.Document'} - >>> view = ConfigureBaseView(m111, TestRequest(form=form)) - >>> m111.target = view.checkCreateTarget() - >>> sorted(resources.keys()) - [u'doc1', u'm1.m11.m111', u'ma07'] - >>> isinstance(resources['m1.m11.m111'], Document) - True - + [u'doc1', u'm1.m11.m111'] + + >>> view.target.title, view.target.token + ('New Resource', '.loops/resources/m1.m11.m111') + A node object provides the targetSchema of its target: >>> from loops.interfaces import IDocumentView >>> from loops.interfaces import IMediaAssetView >>> IDocumentView.providedBy(m111) - True - >>> IMediaAssetView.providedBy(m111) False + >>> IMediaAssetView.providedBy(m111) + True >>> m111.target = None >>> IDocumentView.providedBy(m111) False - >>> m111.target = resources['ma07'] + >>> m111.target = resources['doc1'] >>> IDocumentView.providedBy(m111) - False - >>> IMediaAssetView.providedBy(m111) True + >>> IMediaAssetView.providedBy(m111) + False A node's target is rendered using the NodeView's renderTargetBody() method. This makes use of a browser view registered for the target interface, @@ -535,7 +466,7 @@ edit form provided by the node: >>> proxy = zapi.getAdapter(m111, IDocumentView) >>> proxy.title = u'Set via proxy' - >>> resources['ma07'].title + >>> resources['doc1'].title u'Set via proxy' If the target object is removed from its container all references @@ -549,7 +480,7 @@ cybertools.relation package.) >>> ztapi.subscribe([Interface, IObjectRemovedEvent], None, ... invalidateRelations) - >>> del resources['ma07'] + >>> del resources['doc1'] >>> m111.target >>> IMediaAssetView.providedBy(m111) False diff --git a/browser/common.py b/browser/common.py index aa09e93..afbe267 100644 --- a/browser/common.py +++ b/browser/common.py @@ -29,6 +29,9 @@ from zope.cachedescriptors.property import Lazy from zope.interface import implements from zope.security.proxy import removeSecurityProxy +from loops import util +from loops.target import getTargetTypes + class BaseView(object): def __init__(self, context, request): @@ -66,12 +69,20 @@ class BaseView(object): @Lazy def typeTitle(self): - return self.context.conceptType.title + voc = util.KeywordVocabulary(getTargetTypes()) + token = '.'.join((self.context.__module__, + self.context.__class__.__name__)) + term = voc.getTermByToken(token) + return term.title @Lazy def typeUrl(self): - return zapi.absoluteURL(self.context.conceptType, self.request) + return None + def viewIterator(self, objs): + request = self.request + for o in objs: + yield BaseView(o, request) class LoopsTerms(object): diff --git a/browser/concept.py b/browser/concept.py index 6916d98..2c8da33 100644 --- a/browser/concept.py +++ b/browser/concept.py @@ -134,6 +134,14 @@ class ConceptView(BaseView): result = [r for r in result if r.conceptType == type] return self.viewIterator(result) + @Lazy + def typeTitle(self): + return self.context.conceptType.title + + @Lazy + def typeUrl(self): + return zapi.absoluteURL(self.context.conceptType, self.request) + def viewIterator(self, objs): request = self.request for o in objs: diff --git a/browser/configure.zcml b/browser/configure.zcml index 387b3c1..2528b24 100644 --- a/browser/configure.zcml +++ b/browser/configure.zcml @@ -35,6 +35,13 @@ permission="zope.View" /> + + diff --git a/browser/edit.pt b/browser/edit.pt index e439812..71f51ae 100644 --- a/browser/edit.pt +++ b/browser/edit.pt @@ -64,7 +64,8 @@
-
+
diff --git a/browser/node.py b/browser/node.py index e226d68..7733965 100644 --- a/browser/node.py +++ b/browser/node.py @@ -24,16 +24,24 @@ $Id$ from zope.cachedescriptors.property import Lazy from zope.app import zapi +from zope.app.catalog.interfaces import ICatalog from zope.app.container.browser.contents import JustContents from zope.app.dublincore.interfaces import ICMFDublinCore +from zope.app.event.objectevent import ObjectCreatedEvent #import zope.configuration.name from zope.dottedname.resolve import resolve +from zope.event import notify from zope.proxy import removeAllProxies from zope.security import canAccess, canWrite from zope.security.proxy import removeSecurityProxy from loops.interfaces import IConcept, IDocument, IMediaAsset from loops.resource import MediaAsset +from loops.target import getTargetTypes +from loops import util +from loops.browser.common import BaseView +from loops.browser.concept import ConceptView + class NodeView(object): @@ -124,12 +132,13 @@ class NodeView(object): return item.context == self.context -class ConfigureBaseView(object): - """ Helper view class for editing/configuring a node, providing the - stuff needed for creating a target object. +class ConfigureView(BaseView): + """ An editing view for configuring a node, optionally creating + a target object. """ def __init__(self, context, request): + #self.context = context self.context = removeSecurityProxy(context) self.request = request @@ -137,53 +146,84 @@ class ConfigureBaseView(object): def loopsRoot(self): return self.context.getLoopsRoot() - def checkCreateTarget(self): - form = self.request.form - if form.get('field.createTarget', False): - root = self.loopsRoot - type = self.request.form.get('field.targetType', - 'loops.resource.MediaAsset') - factory = resolve(type) - uri = self.request.form.get('field.targetUri', None) - if uri: - path = uri.split('/') - # TODO: check for .loops prefix - containerName = path[-2] - name = path[-1] - container = root[containerName] - else: - container = ('.resource.' in type and root.getResourceManager() - or root.getConceptManager()) - viewManagerPath = zapi.getPath(root.getViewManager()) - name = zapi.getPath(self.context)[len(viewManagerPath)+1:] - name = name.replace('/', '.') - # check for duplicates: - num = 1 - basename = name - while name in container: - name = '%s-%d' % (basename, num) - num += 1 - # create target: - container[name] = factory() - target = container[name] - # set possibly new target uri in request for further processing: - targetUri = self.loopsRoot.getLoopsUri(target) - form['field.target'] = targetUri - return target - - -class ConfigureView(object): - """ An editing view for configuring a node, optionally creating - a target object. - """ - - def __init__(self, context, request): - super(ConfigureView, self).__init__(context, request) - self.delegate = ConfigureBaseView(context, request) + @property + def target(self): + target = self.context.target + if target is not None: + if IConcept.providedBy(target): + return ConceptView(target, self.request) + return BaseView(target, self.request) + return None def update(self): - if self.update_status is not None: - return self.update_status - self.delegate.checkCreateTarget() - return super(ConfigureView, self).update() + request = self.request + action = request.get('action') + if action is None or action == 'search': + return True + if action == 'create': + return self.createAndAssign() + if action == 'assign': + token = request.get('token') + if token: + target = self.loopsRoot.loopsTraverse(token) + else: + target = None + self.context.target = target + # TODO: raise error + return True + + def createAndAssign(self): + form = self.request.form + root = self.loopsRoot + type = self.request.form.get('create.type', + 'loops.resource.MediaAsset') + factory = resolve(type) + if '.resource.' in type: + container = root.getResourceManager() + else: + container = root.getConceptManager() + name = form.get('create.name', '') + if not name: + viewManagerPath = zapi.getPath(root.getViewManager()) + name = zapi.getPath(self.context)[len(viewManagerPath)+1:] + name = name.replace('/', '.') + # check for duplicates: + num = 1 + basename = name + while name in container: + name = '%s-%d' % (basename, num) + num += 1 + container[name] = removeSecurityProxy(factory()) + target = container[name] + target.title = form.get('create.title', u'') + notify(ObjectCreatedEvent(target)) + self.context.target = target + return True + + def targetTypes(self): + return util.KeywordVocabulary(getTargetTypes()) + + @Lazy + def search(self): + request = self.request + if request.get('action') != 'search': + return [] + searchTerm = request.get('searchTerm', None) + if searchTerm: + cat = zapi.getUtility(ICatalog) + result = cat.searchResults(loops_searchableText=searchTerm) + else: + result = (list(self.loopsRoot.getConceptManager().values()) + + list(self.loopsRoot.getResourceManager().values())) + return list(self.viewIterator(result)) + + def viewIterator(self, objs): + request = self.request + for o in objs: + if o == self.context.target: + continue + if IConcept.providedBy(o): + yield ConceptView(o, request) + else: + yield BaseView(o, request) diff --git a/browser/node_target.pt b/browser/node_target.pt new file mode 100644 index 0000000..6b2065b --- /dev/null +++ b/browser/node_target.pt @@ -0,0 +1,43 @@ + + + + +
+ +

Node Title


+ +
+ No target assigned + + Currently assigned target: + Document xy + +
+ +
+ +
+ +
+ + + + + +
+ +
+ + + diff --git a/browser/target_macros.pt b/browser/target_macros.pt new file mode 100644 index 0000000..39a5a91 --- /dev/null +++ b/browser/target_macros.pt @@ -0,0 +1,146 @@ + + + + +
+ Listing + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
 TitleType
+ + + + Title + + + + Type + +
+
+ + +
+
+
+
+ + + +
+ Create Target +
+ +
+ Name +   + Title +   + Type + +

+
+ +
+
+
+
+ + + +
+ +
+ Search Term + + Type + +
+
+ +
+
+
+ + + diff --git a/configure.zcml b/configure.zcml index a7af217..fbc2e0d 100644 --- a/configure.zcml +++ b/configure.zcml @@ -49,7 +49,8 @@ + attributes="getLoopsUri loopsTraverse getConceptManager + getResourceManager getViewManager" /> diff --git a/target.py b/target.py index 0b08676..8ff84f5 100644 --- a/target.py +++ b/target.py @@ -26,6 +26,7 @@ $Id$ from zope.app import zapi from zope.cachedescriptors.property import Lazy from zope.component import adapts +from zope.i18nmessageid import MessageFactory from zope.interface import implements from zope import schema from zope.security.proxy import removeSecurityProxy @@ -36,6 +37,8 @@ from loops.interfaces import IDocumentView, IMediaAssetView from loops.interfaces import IView from loops.interfaces import IConcept, IConceptView +_ = MessageFactory('loops') + # proxies for accessing target objects from views/nodes @@ -141,3 +144,9 @@ class QueryableTargetSource(object): def __contains__(self, value): return value in self.resources.values() or value in self.concepts.values() + +def getTargetTypes(): + return (('loops.concept.Concept', _(u'Concept')), + ('loops.resource.Document', _(u'Document')), + ('loops.resource.MediaAsset', _(u'Media Asset')), + )