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:
		
							parent
							
								
									e86c3e331a
								
							
						
					
					
						commit
						a0948469d4
					
				
					 10 changed files with 346 additions and 149 deletions
				
			
		
							
								
								
									
										111
									
								
								README.txt
									
										
									
									
									
								
							
							
						
						
									
										111
									
								
								README.txt
									
										
									
									
									
								
							| 
						 | 
					@ -390,113 +390,44 @@ Node Views
 | 
				
			||||||
  ...     print item.url, view.selected(item)
 | 
					  ...     print item.url, view.selected(item)
 | 
				
			||||||
  http://127.0.0.1/loops/views/m1/m11 True
 | 
					  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
 | 
					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.
 | 
					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.browser.node import ConfigureView
 | 
				
			||||||
  >>> from loops.view import NodeConfigAdapter
 | 
					  >>> form = {'action': 'create', 'create.title': 'New Resource',
 | 
				
			||||||
  >>> ztapi.provideAdapter(INode, INodeConfigSchema, NodeConfigAdapter)
 | 
					  ...         'create.type': 'loops.resource.MediaAsset',}
 | 
				
			||||||
  >>> nodeConfig = INodeConfigSchema(m111)
 | 
					  >>> view = ConfigureView(m111, TestRequest(form = form))
 | 
				
			||||||
 | 
					  >>> sorted((t.token, t.title) for t in view.targetTypes())
 | 
				
			||||||
  >>> nodeConfig.title = u'New title for m111'
 | 
					  [('loops.concept.Concept', u'Concept'),
 | 
				
			||||||
  >>> nodeConfig.title
 | 
					      ('loops.resource.Document', u'Document'),
 | 
				
			||||||
  u'New title for m111'
 | 
					      ('loops.resource.MediaAsset', u'Media Asset')]
 | 
				
			||||||
  >>> m111.title
 | 
					  >>> view.update()
 | 
				
			||||||
  u'New title for m111'
 | 
					 | 
				
			||||||
  >>> nodeConfig.target = doc1
 | 
					 | 
				
			||||||
  >>> m111.target is doc1
 | 
					 | 
				
			||||||
  True
 | 
					  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())
 | 
					  >>> sorted(resources.keys())
 | 
				
			||||||
  [u'doc1']
 | 
					  [u'doc1', u'm1.m11.m111']
 | 
				
			||||||
  >>> 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,
 | 
					  >>> view.target.title, view.target.token
 | 
				
			||||||
  ...         'field.targetType': 'loops.resource.Document'}
 | 
					  ('New Resource', '.loops/resources/m1.m11.m111')
 | 
				
			||||||
  >>> 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
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
A node object provides the targetSchema of its target:
 | 
					A node object provides the targetSchema of its target:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  >>> from loops.interfaces import IDocumentView
 | 
					  >>> from loops.interfaces import IDocumentView
 | 
				
			||||||
  >>> from loops.interfaces import IMediaAssetView
 | 
					  >>> from loops.interfaces import IMediaAssetView
 | 
				
			||||||
  >>> IDocumentView.providedBy(m111)
 | 
					  >>> IDocumentView.providedBy(m111)
 | 
				
			||||||
  True
 | 
					 | 
				
			||||||
  >>> IMediaAssetView.providedBy(m111)
 | 
					 | 
				
			||||||
  False
 | 
					  False
 | 
				
			||||||
 | 
					  >>> IMediaAssetView.providedBy(m111)
 | 
				
			||||||
 | 
					  True
 | 
				
			||||||
  >>> m111.target = None
 | 
					  >>> m111.target = None
 | 
				
			||||||
  >>> IDocumentView.providedBy(m111)
 | 
					  >>> IDocumentView.providedBy(m111)
 | 
				
			||||||
  False
 | 
					  False
 | 
				
			||||||
  >>> m111.target = resources['ma07']
 | 
					  >>> m111.target = resources['doc1']
 | 
				
			||||||
  >>> IDocumentView.providedBy(m111)
 | 
					  >>> IDocumentView.providedBy(m111)
 | 
				
			||||||
  False
 | 
					 | 
				
			||||||
  >>> IMediaAssetView.providedBy(m111)
 | 
					 | 
				
			||||||
  True
 | 
					  True
 | 
				
			||||||
 | 
					  >>> IMediaAssetView.providedBy(m111)
 | 
				
			||||||
 | 
					  False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
A node's target is rendered using the NodeView's renderTargetBody()
 | 
					A node's target is rendered using the NodeView's renderTargetBody()
 | 
				
			||||||
method. This makes use of a browser view registered for the target interface,
 | 
					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 = zapi.getAdapter(m111, IDocumentView)
 | 
				
			||||||
  >>> proxy.title = u'Set via proxy'
 | 
					  >>> proxy.title = u'Set via proxy'
 | 
				
			||||||
  >>> resources['ma07'].title
 | 
					  >>> resources['doc1'].title
 | 
				
			||||||
  u'Set via proxy'
 | 
					  u'Set via proxy'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
If the target object is removed from its container all references
 | 
					If the target object is removed from its container all references
 | 
				
			||||||
| 
						 | 
					@ -549,7 +480,7 @@ cybertools.relation package.)
 | 
				
			||||||
  >>> ztapi.subscribe([Interface, IObjectRemovedEvent], None,
 | 
					  >>> ztapi.subscribe([Interface, IObjectRemovedEvent], None,
 | 
				
			||||||
  ...                 invalidateRelations)
 | 
					  ...                 invalidateRelations)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  >>> del resources['ma07']
 | 
					  >>> del resources['doc1']
 | 
				
			||||||
  >>> m111.target
 | 
					  >>> m111.target
 | 
				
			||||||
  >>> IMediaAssetView.providedBy(m111)
 | 
					  >>> IMediaAssetView.providedBy(m111)
 | 
				
			||||||
  False
 | 
					  False
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -29,6 +29,9 @@ from zope.cachedescriptors.property import Lazy
 | 
				
			||||||
from zope.interface import implements
 | 
					from zope.interface import implements
 | 
				
			||||||
from zope.security.proxy import removeSecurityProxy
 | 
					from zope.security.proxy import removeSecurityProxy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					from loops import util
 | 
				
			||||||
 | 
					from loops.target import getTargetTypes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BaseView(object):
 | 
					class BaseView(object):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, context, request):
 | 
					    def __init__(self, context, request):
 | 
				
			||||||
| 
						 | 
					@ -66,12 +69,20 @@ class BaseView(object):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Lazy
 | 
					    @Lazy
 | 
				
			||||||
    def typeTitle(self):
 | 
					    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
 | 
					    @Lazy
 | 
				
			||||||
    def typeUrl(self):
 | 
					    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):
 | 
					class LoopsTerms(object):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -134,6 +134,14 @@ class ConceptView(BaseView):
 | 
				
			||||||
            result = [r for r in result if r.conceptType == type]
 | 
					            result = [r for r in result if r.conceptType == type]
 | 
				
			||||||
        return self.viewIterator(result)
 | 
					        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):
 | 
					    def viewIterator(self, objs):
 | 
				
			||||||
        request = self.request
 | 
					        request = self.request
 | 
				
			||||||
        for o in objs:
 | 
					        for o in objs:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -35,6 +35,13 @@
 | 
				
			||||||
      permission="zope.View"
 | 
					      permission="zope.View"
 | 
				
			||||||
      />
 | 
					      />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  <page
 | 
				
			||||||
 | 
					      for="*"
 | 
				
			||||||
 | 
					      name="target_macros"
 | 
				
			||||||
 | 
					      template="target_macros.pt"
 | 
				
			||||||
 | 
					      permission="zope.View"
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <!-- loops top-level container -->
 | 
					  <!-- loops top-level container -->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <addform
 | 
					  <addform
 | 
				
			||||||
| 
						 | 
					@ -331,10 +338,10 @@
 | 
				
			||||||
  <editform
 | 
					  <editform
 | 
				
			||||||
      label="Configure Node"
 | 
					      label="Configure Node"
 | 
				
			||||||
      name="configure.html"
 | 
					      name="configure.html"
 | 
				
			||||||
      schema="loops.interfaces.INodeConfigSchema"
 | 
					      schema="loops.interfaces.INode"
 | 
				
			||||||
      fields="title description nodeType target createTarget targetUri targetType"
 | 
					      fields="title description nodeType target"
 | 
				
			||||||
      for="loops.interfaces.INode"
 | 
					      for="loops.interfaces.INode"
 | 
				
			||||||
      template="edit.pt"
 | 
					      template="node_target.pt"
 | 
				
			||||||
      class="loops.browser.node.ConfigureView"
 | 
					      class="loops.browser.node.ConfigureView"
 | 
				
			||||||
      permission="zope.ManageContent">
 | 
					      permission="zope.ManageContent">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -64,7 +64,8 @@
 | 
				
			||||||
        <div class="separator"></div>
 | 
					        <div class="separator"></div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      <div class="row">
 | 
					      <div metal:define-macro="submit_button"
 | 
				
			||||||
 | 
					           class="row">
 | 
				
			||||||
        <div class="controls">
 | 
					        <div class="controls">
 | 
				
			||||||
          <input type="submit" name="UPDATE_SUBMIT" value="Change"
 | 
					          <input type="submit" name="UPDATE_SUBMIT" value="Change"
 | 
				
			||||||
                 i18n:attributes="value submit-button;" />
 | 
					                 i18n:attributes="value submit-button;" />
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										140
									
								
								browser/node.py
									
										
									
									
									
								
							
							
						
						
									
										140
									
								
								browser/node.py
									
										
									
									
									
								
							| 
						 | 
					@ -24,16 +24,24 @@ $Id$
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from zope.cachedescriptors.property import Lazy
 | 
					from zope.cachedescriptors.property import Lazy
 | 
				
			||||||
from zope.app import zapi
 | 
					from zope.app import zapi
 | 
				
			||||||
 | 
					from zope.app.catalog.interfaces import ICatalog
 | 
				
			||||||
from zope.app.container.browser.contents import JustContents
 | 
					from zope.app.container.browser.contents import JustContents
 | 
				
			||||||
from zope.app.dublincore.interfaces import ICMFDublinCore
 | 
					from zope.app.dublincore.interfaces import ICMFDublinCore
 | 
				
			||||||
 | 
					from zope.app.event.objectevent import ObjectCreatedEvent
 | 
				
			||||||
#import zope.configuration.name
 | 
					#import zope.configuration.name
 | 
				
			||||||
from zope.dottedname.resolve import resolve
 | 
					from zope.dottedname.resolve import resolve
 | 
				
			||||||
 | 
					from zope.event import notify
 | 
				
			||||||
from zope.proxy import removeAllProxies
 | 
					from zope.proxy import removeAllProxies
 | 
				
			||||||
from zope.security import canAccess, canWrite
 | 
					from zope.security import canAccess, canWrite
 | 
				
			||||||
from zope.security.proxy import removeSecurityProxy
 | 
					from zope.security.proxy import removeSecurityProxy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from loops.interfaces import IConcept, IDocument, IMediaAsset
 | 
					from loops.interfaces import IConcept, IDocument, IMediaAsset
 | 
				
			||||||
from loops.resource import MediaAsset
 | 
					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):
 | 
					class NodeView(object):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -124,12 +132,13 @@ class NodeView(object):
 | 
				
			||||||
        return item.context == self.context
 | 
					        return item.context == self.context
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ConfigureBaseView(object):
 | 
					class ConfigureView(BaseView):
 | 
				
			||||||
    """ Helper view class for editing/configuring a node, providing the
 | 
					    """ An editing view for configuring a node, optionally creating
 | 
				
			||||||
        stuff needed for creating a target object.
 | 
					        a target object.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, context, request):
 | 
					    def __init__(self, context, request):
 | 
				
			||||||
 | 
					        #self.context = context
 | 
				
			||||||
        self.context = removeSecurityProxy(context)
 | 
					        self.context = removeSecurityProxy(context)
 | 
				
			||||||
        self.request = request
 | 
					        self.request = request
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -137,53 +146,84 @@ class ConfigureBaseView(object):
 | 
				
			||||||
    def loopsRoot(self):
 | 
					    def loopsRoot(self):
 | 
				
			||||||
        return self.context.getLoopsRoot()
 | 
					        return self.context.getLoopsRoot()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def checkCreateTarget(self):
 | 
					    @property
 | 
				
			||||||
        form = self.request.form
 | 
					    def target(self):
 | 
				
			||||||
        if form.get('field.createTarget', False):
 | 
					        target = self.context.target
 | 
				
			||||||
            root = self.loopsRoot
 | 
					        if target is not None:
 | 
				
			||||||
            type = self.request.form.get('field.targetType',
 | 
					            if IConcept.providedBy(target):
 | 
				
			||||||
                                         'loops.resource.MediaAsset')
 | 
					                return ConceptView(target, self.request)
 | 
				
			||||||
            factory = resolve(type)
 | 
					            return BaseView(target, self.request)
 | 
				
			||||||
            uri = self.request.form.get('field.targetUri', None)
 | 
					        return 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)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def update(self):
 | 
					    def update(self):
 | 
				
			||||||
        if self.update_status is not None:
 | 
					        request = self.request
 | 
				
			||||||
            return self.update_status
 | 
					        action = request.get('action')
 | 
				
			||||||
        self.delegate.checkCreateTarget()
 | 
					        if action is None or action == 'search':
 | 
				
			||||||
        return super(ConfigureView, self).update()
 | 
					            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
									
								
							
							
						
						
									
										43
									
								
								browser/node_target.pt
									
										
									
									
									
										Normal 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
									
								
							
							
						
						
									
										146
									
								
								browser/target_macros.pt
									
										
									
									
									
										Normal 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> </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" /> 
 | 
				
			||||||
 | 
					        <span i18n:translate="">Title</span>
 | 
				
			||||||
 | 
					        <input name="create.title" size="30"
 | 
				
			||||||
 | 
					               tal:attributes="value nothing" /> 
 | 
				
			||||||
 | 
					        <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>
 | 
				
			||||||
| 
						 | 
					@ -49,7 +49,8 @@
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    <require
 | 
					    <require
 | 
				
			||||||
        permission="zope.View"
 | 
					        permission="zope.View"
 | 
				
			||||||
        attributes="getLoopsUri loopsTraverse getConceptManager" />
 | 
					        attributes="getLoopsUri loopsTraverse getConceptManager
 | 
				
			||||||
 | 
					                    getResourceManager getViewManager" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  </content>
 | 
					  </content>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -26,6 +26,7 @@ $Id$
 | 
				
			||||||
from zope.app import zapi
 | 
					from zope.app import zapi
 | 
				
			||||||
from zope.cachedescriptors.property import Lazy
 | 
					from zope.cachedescriptors.property import Lazy
 | 
				
			||||||
from zope.component import adapts
 | 
					from zope.component import adapts
 | 
				
			||||||
 | 
					from zope.i18nmessageid import MessageFactory
 | 
				
			||||||
from zope.interface import implements
 | 
					from zope.interface import implements
 | 
				
			||||||
from zope import schema
 | 
					from zope import schema
 | 
				
			||||||
from zope.security.proxy import removeSecurityProxy
 | 
					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 IView
 | 
				
			||||||
from loops.interfaces import IConcept, IConceptView
 | 
					from loops.interfaces import IConcept, IConceptView
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					_ = MessageFactory('loops')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# proxies for accessing target objects from views/nodes
 | 
					# proxies for accessing target objects from views/nodes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -141,3 +144,9 @@ class QueryableTargetSource(object):
 | 
				
			||||||
    def __contains__(self, value):
 | 
					    def __contains__(self, value):
 | 
				
			||||||
        return value in self.resources.values() or value in self.concepts.values()
 | 
					        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')),
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue