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
This commit is contained in:
helmutm 2006-02-25 18:11:24 +00:00
parent e86c3e331a
commit a0948469d4
10 changed files with 346 additions and 149 deletions

View file

@ -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', <loops.resource.Document...>)
>>> term = terms.getTerm(cc1)
>>> term.token, term.title, term.value
('.loops/concepts/cc1', u'cc1', <loops.concept.Concept...>)
>>> 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

View file

@ -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):

View file

@ -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:

View file

@ -35,6 +35,13 @@
permission="zope.View"
/>
<page
for="*"
name="target_macros"
template="target_macros.pt"
permission="zope.View"
/>
<!-- loops top-level container -->
<addform
@ -331,10 +338,10 @@
<editform
label="Configure Node"
name="configure.html"
schema="loops.interfaces.INodeConfigSchema"
fields="title description nodeType target createTarget targetUri targetType"
schema="loops.interfaces.INode"
fields="title description nodeType target"
for="loops.interfaces.INode"
template="edit.pt"
template="node_target.pt"
class="loops.browser.node.ConfigureView"
permission="zope.ManageContent">

View file

@ -64,7 +64,8 @@
<div class="separator"></div>
</div>
<div class="row">
<div metal:define-macro="submit_button"
class="row">
<div class="controls">
<input type="submit" name="UPDATE_SUBMIT" value="Change"
i18n:attributes="value submit-button;" />

View file

@ -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)

43
browser/node_target.pt Normal file
View file

@ -0,0 +1,43 @@
<tal:tag condition="view/update" />
<html metal:use-macro="context/@@standard_macros/view"
i18n:domain="zope">
<head></head>
<body>
<div metal:fill-slot="body"
tal:define="target view/target">
<h1 tal:content="context/title">Node Title</h1><br />
<div>
<span i18n:translate=""
tal:condition="not:target">No target assigned</span>
<tal:target condition="target">
<span i18n:translate="">Currently assigned target</span>:
<a href="#"
tal:attributes="href string:${target/url}/@@SelectedManagementView.html"
tal:content="target/title">Document xy</a>
</tal:target>
</div>
<div style="padding-right:20px">
<metal:create use-macro="views/target_macros/create" />
</div>
<div tal:define="items view/search;
action string:assign;
target nocall:context/target;
summary string:Assignment candidates;
legend string:Search;
buttonText string:Assign Target;"
style="padding-right:20px">
<metal:assign use-macro="views/target_macros/listing">
<metal:search fill-slot="topActions">
<metal:block use-macro="views/target_macros/search" />
</metal:search>
</metal:assign>
</div>
</div>
</body>
</html>

146
browser/target_macros.pt Normal file
View file

@ -0,0 +1,146 @@
<html i18n:domain="loops">
<metal:assignments define-macro="listing">
<fieldset>
<legend tal:content="legend"
i18n:translate="">Listing</legend>
<metal:top define-slot="topActions" />
<form metal:define-macro="listing_form"
method="post" name="listing" action="."
tal:attributes="action request/URL"
tal:condition="items">
<input type="hidden" name="action" value="assign"
tal:attributes="value action" />
<table class="listing" summary="Currently assigned"
i18n:attributes="summary"
tal:attributes="summary summary">
<thead>
<tr>
<th>&nbsp;</th>
<th i18n:translate="label_title">Title</th>
<th i18n:translate="label_type">Type</th>
</tr>
</thead>
<tbody tal:define="target view/target;
targetToken python: target and target.token or None">
<tal:none define="item nothing; title string:None; token nothing;
type nothing">
<metal:none use-macro="views/target_macros/list_item_tr" />
</tal:none>
<tal:current define="item target"
condition="item">
<metal:current use-macro="views/target_macros/list_item" />
</tal:current>
<tal:items repeat="item items">
<metal:item define-macro="list_item"
tal:define="title item/title;
token item/token;
type item/typeTitle">
<tr metal:define-macro="list_item_tr">
<td class="field">
<input class="formSelection"
type="radio" name="token" id="#" value=""
tal:attributes="value token;
checked python: targetToken == token" />
</td>
<td>
<a href="#"
tal:omit-tag="not:item"
tal:content="title"
tal:attributes="href
string:${item/url|nothing}/@@SelectedManagementView.html">
Title
</a>
</td>
<td>
<a tal:condition="type"
href="#"
tal:attributes="href
string:${item/typeUrl}/@@SelectedManagementView.html"
tal:omit-tag="not:item/typeUrl">
<span tal:replace="type">Type</span>
</a>
</td>
</tr>
</metal:item>
</tal:items>
</tbody>
</table>
<div class="formControls">
<input class="context" type="submit" name="form.button.submit"
value="Change assignment"
i18n:attributes="value"
tal:attributes="value buttonText" />
<metal:buttons define-slot="specialButtons" />
</div>
</form>
</fieldset>
</metal:assignments>
<metal:create define-macro="create">
<fieldset>
<legend i18n:translate="">Create Target</legend>
<form method="post" name="listing" action="."
tal:attributes="action request/URL">
<input type="hidden" name="action" value="create" />
<div class="row">
<span i18n:translate="">Name</span>
<input name="create.name" size="15"
tal:attributes="value nothing" />&nbsp;
<span i18n:translate="">Title</span>
<input name="create.title" size="30"
tal:attributes="value nothing" />&nbsp;
<span i18n:translate="">Type</span>
<select name="create.type">
<tal:types repeat="type view/targetTypes">
<option value=".loops/concepts/topic"
i18n:translate=""
tal:attributes="value type/token"
tal:content="type/title">Topic</option>
</tal:types>
</select>
</div><br />
<div class="formControls">
<input class="context" type="submit" name="form.button.submit"
value="Create Target"
i18n:attributes="value" />
</div>
</form>
</fieldset>
</metal:create>
<metal:search define-macro="search">
<form method="post" name="listing" action="."
tal:attributes="action request/URL">
<input type="hidden" name="action" value="search" />
<div class="row"
tal:define="searchTerm request/searchTerm | nothing;
searchType request/searchType | nothing;">
<span i18n:translate="">Search Term</span>
<input name="searchTerm"
tal:attributes="value searchTerm" />
<span i18n:translate="">Type</span>
<select name="searchType">
<option value="*">Any</option>
<tal:types repeat="type view/targetTypes">
<option value=".loops/concepts/topic"
i18n:translate=""
tal:attributes="value type/token;
selected python: type.token == searchType"
tal:content="type/title">Topic</option>
</tal:types>
</select>
</div>
<div class="formControls">
<input class="context" type="submit" name="form.button.submit"
value="Search"
i18n:attributes="value" />
</div>
</form>
</metal:search>
</html>

View file

@ -49,7 +49,8 @@
<require
permission="zope.View"
attributes="getLoopsUri loopsTraverse getConceptManager" />
attributes="getLoopsUri loopsTraverse getConceptManager
getResourceManager getViewManager" />
</content>

View file

@ -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')),
)