Assignment of resources to concepts and vice versa

git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@1108 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
helmutm 2006-03-04 14:50:16 +00:00
parent fc6946be95
commit d6b7f5bc1f
10 changed files with 410 additions and 47 deletions

View file

@ -121,7 +121,7 @@ a special concept type.
Concept Views
-------------
>>> from loops.browser.concept import ConceptView
>>> from loops.browser.concept import ConceptView, ConceptConfigureView
>>> view = ConceptView(cc1, TestRequest())
>>> children = list(view.children())
@ -136,18 +136,19 @@ of URIs to item and the predicate of the relationship:
>>> [c.token for c in children]
['.loops/concepts/cc2:.loops/concepts/standard']
The concept view allows updating the underlying context object:
There is also a concept configuration view that allows updating the
underlying context object:
>>> cc3 = Concept(u'loops for Zope 3')
>>> concepts['cc3'] = cc3
>>> view = ConceptView(cc1,
>>> view = ConceptConfigureView(cc1,
... TestRequest(action='assign', tokens=['.loops/concepts/cc3']))
>>> view.update()
True
>>> sorted(c.title for c in cc1.getChildren())
[u'Zope 3', u'loops for Zope 3']
>>> view = ConceptView(cc1,
>>> view = ConceptConfigureView(cc1,
... TestRequest(action='remove', qualifier='children',
... tokens=['.loops/concepts/cc2:.loops/concepts/standard']))
>>> view.update()
@ -159,14 +160,14 @@ We can also create a new concept and assign it:
>>> params = {'action': 'create', 'create.name': 'cc4',
... 'create.title': u'New concept'}
>>> view = ConceptView(cc1, TestRequest(**params))
>>> view = ConceptConfigureView(cc1, TestRequest(**params))
>>> view.update()
True
>>> sorted(c.title for c in cc1.getChildren())
[u'New concept', u'loops for Zope 3']
The concept view provides methods for displaying concept types and
predicates:
The concept configuration view provides methods for displaying concept
types and predicates:
>>> from zope.publisher.interfaces.browser import IBrowserRequest
>>> from loops.browser.common import LoopsTerms
@ -257,15 +258,33 @@ We can associate a resource with a concept by assigning it to the concept:
>>> list(res)
[<loops.resource.Document ...>]
The resource also provides access to the associated concepts (or views, see
below) via the getClients() method:
The concept configuration view discussed above also manages the relations
from concepts to resources:
>>> conc = doc1.getClients()
>>> len(conc)
>>> len(cc1.getResources())
1
>>> conc[0] is cc1
>>> form = dict(action='remove', qualifier='resources',
... tokens=['.loops/resources/doc1:.loops/concepts/standard'])
>>> view = ConceptConfigureView(cc1, TestRequest(form=form))
>>> [zapi.getName(r.context) for r in view.resources()]
[u'doc1']
>>> view.update()
True
>>> len(cc1.getResources())
0
>>> form = dict(action='assign', assignAs='resource',
... tokens=['.loops/resources/doc1'])
>>> view = ConceptConfigureView(cc1, TestRequest(form=form))
>>> view.update()
True
>>> len(cc1.getResources())
1
These relations may also be managed starting from a resource using
the resource configuration view:
>>> from loops.browser.resource import ResourceConfigureView
Index attributes adapter
------------------------
@ -393,6 +412,16 @@ out - this is usually done through ZCML.)
>>> m111.target is cc2
True
A resource provides access to the associated views/nodes via the
getClients() method:
>>> len(doc1.getClients())
0
>>> m112.target = doc1
>>> nodes = doc1.getClients()
>>> nodes[0] is m112
True
Node Views
----------

View file

@ -28,6 +28,7 @@ from zope.app.dublincore.interfaces import ICMFDublinCore
from zope.app.event.objectevent import ObjectCreatedEvent
from zope.app.form.browser.interfaces import ITerms
from zope.cachedescriptors.property import Lazy
from zope.dottedname.resolve import resolve
from zope.event import notify
from zope.interface import implements
from zope.publisher.interfaces import BadRequest
@ -35,7 +36,10 @@ from zope.publisher.interfaces.browser import IBrowserRequest
from zope import schema
from zope.schema.interfaces import IIterableSource
from zope.security.proxy import removeSecurityProxy
from loops.interfaces import IConcept
from loops.concept import Concept, ConceptTypeSourceList, PredicateSourceList
from loops.resource import getResourceTypes, getResourceTypesForSearch
from loops.target import getTargetTypes
from loops.browser.common import BaseView, LoopsTerms
from loops import util
@ -50,6 +54,13 @@ class ConceptView(BaseView):
for r in self.context.getParentRelations():
yield ConceptRelationView(r, self.request)
def resources(self):
for r in self.context.getResourceRelations():
yield ConceptResourceRelationView(r, self.request, contextIsSecond=True)
class ConceptConfigureView(ConceptView):
def update(self):
request = self.request
action = request.get('action')
@ -75,6 +86,8 @@ class ConceptView(BaseView):
self.context.assignChild(removeSecurityProxy(concept), predicate)
elif assignAs == 'parent':
self.context.assignParent(removeSecurityProxy(concept), predicate)
elif assignAs == 'resource':
self.context.assignResource(removeSecurityProxy(concept), predicate)
else:
raise(BadRequest, 'Illegal assignAs parameter: %s.' % assignAs)
elif action == 'remove':
@ -84,6 +97,8 @@ class ConceptView(BaseView):
self.context.deassignParent(concept, [predicate])
elif qualifier == 'children':
self.context.deassignChild(concept, [predicate])
elif qualifier == 'resources':
self.context.deassignResource(concept, [predicate])
else:
raise(BadRequest, 'Illegal qualifier: %s.' % qualifier)
else:
@ -97,8 +112,13 @@ class ConceptView(BaseView):
raise(BadRequest, 'Empty name.')
title = request.get('create.title', u'')
conceptType = request.get('create.type')
concept = Concept(title)
container = self.loopsRoot.getConceptManager()
if conceptType and conceptType.startswith('loops.resource.'):
factory = resolve(conceptType)
concept = factory(title)
container = self.loopsRoot.getResourceManager()
else:
concept = Concept(title)
container = self.loopsRoot.getConceptManager()
container[name] = concept
if conceptType:
ctype = self.loopsRoot.loopsTraverse(conceptType)
@ -113,6 +133,8 @@ class ConceptView(BaseView):
self.context.assignChild(removeSecurityProxy(concept), predicate)
elif assignAs == 'parent':
self.context.assignParent(removeSecurityProxy(concept), predicate)
elif assignAs == 'resource':
self.context.assignResource(removeSecurityProxy(concept), predicate)
else:
raise(BadRequest, 'Illegal assignAs parameter: %s.' % assignAs)
@ -153,7 +175,10 @@ class ConceptView(BaseView):
def viewIterator(self, objs):
request = self.request
for o in objs:
yield ConceptView(o, request)
if IConcept.providedBy(o):
yield ConceptConfigureView(o, request)
else:
yield BaseView(o, request)
def conceptTypes(self):
types = ConceptTypeSourceList(self.context)
@ -171,6 +196,13 @@ class ConceptView(BaseView):
def getConceptTypeTokenForSearch(self, ct):
return ct is None and 'unknown' or zapi.getName(ct)
def resourceTypes(self):
return util.KeywordVocabulary(getResourceTypes())
def resourceTypesForSearch(self):
return util.KeywordVocabulary(getResourceTypesForSearch())
def predicates(self):
preds = PredicateSourceList(self.context)
terms = zapi.getMultiAdapter((preds, self.request), ITerms)
@ -188,7 +220,6 @@ class ConceptRelationView(object):
self.context = relation.first
self.other = relation.second
self.predicate = relation.predicate
self.conceptType = self.context.conceptType
self.request = request
@Lazy
@ -208,6 +239,10 @@ class ConceptRelationView(object):
return ':'.join((self.loopsRoot.getLoopsUri(self.context),
self.loopsRoot.getLoopsUri(self.predicate)))
@Lazy
def conceptType(self):
return self.context.conceptType
@Lazy
def typeTitle(self):
return self.conceptType.title
@ -224,3 +259,23 @@ class ConceptRelationView(object):
def predicateUrl(self):
return zapi.absoluteURL(self.predicate, self.request)
class ConceptResourceRelationView(ConceptRelationView):
@Lazy
def conceptType(self):
return None
@Lazy
def typeTitle(self):
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 ''

View file

@ -0,0 +1,87 @@
<tal:tag condition="view/update" />
<html metal:use-macro="context/@@standard_macros/view"
i18n:domain="loops">
<body>
<div metal:fill-slot="body">
<h1 tal:content="context/title">Concept Title</h1><br />
<div tal:define="items view/resources;
action string:remove;
qualifier string:parents;
summary string:Currently assigned resources;
legend string:Resources;
showPredicate string:yes;
buttonText string:Remove Resources;"
style="float:left; padding-right:20px">
<metal:parents use-macro="views/relation_macros/listing" />
</div>
<div tal:define="legend string:Create Resource;
buttonText string:Create Resource"
style="padding-right:20px; clear:left">
<metal:create use-macro="views/relation_macros/create">
<select name="create.type" metal:fill-slot="types">
<tal:types repeat="type view/resourceTypes">
<option value="loops.resource.Document"
i18n:translate=""
tal:attributes="value type/token"
tal:content="type/title">Document</option>
</tal:types>
</select>
<metal:control fill-slot="control">
<input class="context" type="submit" name="form.button.submit"
value="Create Object"
i18n:attributes="value"
tal:attributes="value buttonText" />
<input type="hidden" name="assignAs" value="resource" />
and assign using Predicate
<select metal:use-macro="views/relation_macros/predicates" />
</metal:control>
</metal:create>
</div>
<div tal:define="items view/search;
action string:assign;
qualifier nothing;
summary string:Assignment candidates;
legend string:Search;
showPredicate nothing;
buttonText string:Assign;"
style="padding-right:20px">
<metal:assign use-macro="views/relation_macros/listing">
<metal:search fill-slot="topActions">
<metal:block use-macro="views/relation_macros/search">
<select name="searchType" metal:fill-slot="types">
<option value="loops:resource:*"
tal:attributes="selected python:
searchType == 'loops:resource:*'">Any</option>
<tal:types repeat="type view/resourceTypesForSearch">
<option value="loops:resource:Document"
i18n:translate=""
tal:attributes="value type/token;
selected python: type.token == searchType"
tal:content="type/title">Topic</option>
</tal:types>
</select>
</metal:block>
</metal:search>
<metal:special fill-slot="specialButtons">
<input type="hidden" name="assignAs" value="resource" />
Object(s) using Predicate
<select name="predicate">
<tal:types repeat="pred view/predicates">
<option value=".loops/concepts/hasType"
i18n:translate=""
tal:attributes="value pred/token"
tal:content="pred/title">Predicate</option>
</tal:types>
</select>
</metal:special>
</metal:assign>
</div>
</div>
</body>
</html>

View file

@ -105,7 +105,7 @@
<pages
for="loops.interfaces.IConcept"
class=".concept.ConceptView"
class=".concept.ConceptConfigureView"
permission="zope.ManageContent">
<page
@ -114,6 +114,12 @@
menu="zmi_views" title="Related Concepts"
/>
<page
name="resources.html"
template="concept_resources.pt"
menu="zmi_views" title="Resources"
/>
</pages>
<editform
@ -163,6 +169,21 @@
add="zope.ManageContent"
/>
<!-- resource in general -->
<pages
for="loops.interfaces.IResource"
class=".resource.ResourceConfigureView"
permission="zope.ManageContent">
<page
name="concepts.html"
template="resource_concepts.pt"
menu="zmi_views" title="Concepts"
/>
</pages>
<!-- document -->
<!--<zope:view

View file

@ -43,6 +43,7 @@
<td>
<a tal:condition="item/typeTitle | nothing"
tal:content="item/typeTitle" href="#"
tal:omit-tag="not:item/typeUrl"
tal:attributes="href
string:${item/typeUrl}/@@SelectedManagementView.html">
Type
@ -85,7 +86,7 @@
<input name="create.title" size="30"
tal:attributes="value nothing" />&nbsp;
<span i18n:translate="">Type</span>
<select name="create.type">
<select name="create.type" metal:define-slot="types">
<tal:types repeat="type view/conceptTypes">
<option value=".loops/concepts/topic"
i18n:translate=""
@ -96,24 +97,26 @@
</select>
</div><br />
<div class="formControls">
<input class="context" type="submit" name="form.button.submit"
value="Create Object"
i18n:attributes="value"
tal:attributes="value buttonText" />
and assign as
<select name="assignAs">
<option value="child" selected i18n:translate="">Child</option>
<option value="parent" i18n:translate="">Parent</option>
</select>
using Predicate
<select name="create.predicate">
<tal:types repeat="pred view/predicates">
<option value=".loops/concepts/hasType"
i18n:translate=""
tal:attributes="value pred/token"
tal:content="pred/title">Predicate</option>
</tal:types>
</select>
<metal:control define-slot="control">
<input class="context" type="submit" name="form.button.submit"
value="Create Object"
i18n:attributes="value"
tal:attributes="value buttonText" />
and assign as
<select name="assignAs">
<option value="child" selected i18n:translate="">Child</option>
<option value="parent" i18n:translate="">Parent</option>
</select>
using Predicate
<select name="create.predicate" metal:define-macro="predicates">
<tal:types repeat="pred view/predicates">
<option value=".loops/concepts/hasType"
i18n:translate=""
tal:attributes="value pred/token"
tal:content="pred/title">Predicate</option>
</tal:types>
</select>
</metal:control>
</div>
</form>
</fieldset>
@ -131,7 +134,7 @@
<input name="searchTerm"
tal:attributes="value searchTerm" />
<span i18n:translate="">Type</span>
<select name="searchType">
<select name="searchType" metal:define-slot="types">
<option value="loops:concept:*"
tal:attributes="selected python:
searchType == 'loops:concept:*'">Any</option>

View file

@ -24,12 +24,15 @@ $Id$
from zope.cachedescriptors.property import Lazy
from zope.app import zapi
from zope.app.catalog.interfaces import ICatalog
from zope.app.dublincore.interfaces import ICMFDublinCore
from zope.proxy import removeAllProxies
from zope.security import canAccess, canWrite
from zope.security.proxy import removeSecurityProxy
from loops.interfaces import IDocument, IMediaAsset
from loops.browser.common import BaseView
from loops.browser.concept import ConceptRelationView, ConceptConfigureView
renderingFactories = {
'text/plain': 'zope.source.plaintext',
@ -40,11 +43,69 @@ renderingFactories = {
}
class DocumentView(object):
class ResourceView(BaseView):
def __init__(self, context, request):
self.context = context
self.request = request
def concepts(self):
for r in self.context.getConceptRelations():
yield ConceptRelationView(r, self.request)
class ResourceConfigureView(ResourceView, ConceptConfigureView):
def update(self):
request = self.request
action = request.get('action')
if action is None:
return True
if action == 'create':
self.createAndAssign()
return True
tokens = request.get('tokens', [])
for token in tokens:
parts = token.split(':')
token = parts[0]
if len(parts) > 1:
relToken = parts[1]
concept = self.loopsRoot.loopsTraverse(token)
if action == 'assign':
predicate = request.get('predicate') or None
if predicate:
predicate = removeSecurityProxy(
self.loopsRoot.loopsTraverse(predicate))
self.context.assignConcept(removeSecurityProxy(concept), predicate)
elif action == 'remove':
predicate = self.loopsRoot.loopsTraverse(relToken)
self.context.deassignConcept(concept, [predicate])
return True
def search(self):
request = self.request
if request.get('action') != 'search':
return []
searchTerm = request.get('searchTerm', None)
searchType = request.get('searchType', None)
result = []
if searchTerm or searchType != 'none':
criteria = {}
if searchTerm:
criteria['loops_title'] = searchTerm
if searchType:
if searchType.endswith('*'):
start = searchType[:-1]
end = start + '\x7f'
else:
start = end = searchType
criteria['loops_type'] = (start, end)
cat = zapi.getUtility(ICatalog)
result = cat.searchResults(**criteria)
else:
result = self.loopsRoot.getConceptManager().values()
if searchType == 'none':
result = [r for r in result if r.conceptType is None]
return self.viewIterator(result)
class DocumentView(ResourceView):
def render(self):
""" Return the rendered content (data) of the context object.

View file

@ -0,0 +1,49 @@
<tal:tag condition="view/update" />
<html metal:use-macro="context/@@standard_macros/view"
i18n:domain="loops">
<body>
<div metal:fill-slot="body">
<h1 tal:content="context/title">Concept Title</h1><br />
<div tal:define="items view/concepts;
action string:remove;
qualifier string:parents;
summary string:Currently assigned objects;
legend string:Parent Concepts;
showPredicate string:yes;
buttonText string:Remove Parents;"
style="padding-right:20px">
<metal:parents use-macro="views/relation_macros/listing" />
</div>
<div tal:define="items view/search;
action string:assign;
qualifier nothing;
summary string:Assignment candidates;
legend string:Search;
showPredicate nothing;
buttonText string:Assign;"
style="padding-right:20px">
<metal:assign use-macro="views/relation_macros/listing">
<metal:search fill-slot="topActions">
<metal:block use-macro="views/relation_macros/search" />
</metal:search>
<metal:special fill-slot="specialButtons">
Object(s) using Predicate
<select name="predicate">
<tal:types repeat="pred view/predicates">
<option value=".loops/concepts/hasType"
i18n:translate=""
tal:attributes="value pred/token"
tal:content="pred/title">Predicate</option>
</tal:types>
</select>
</metal:special>
</metal:assign>
</div>
</div>
</body>
</html>

View file

@ -173,7 +173,7 @@ class Concept(Contained, Persistent):
if predicate is None:
predicate = self.getConceptManager().getDefaultPredicate()
registry = zapi.getUtility(IRelationRegistry)
registry.register(ResourceRelation(self, resource, predicate))
registry.register(ResourceRelation(self, resource))
# TODO (?): avoid duplicates
def deassignResource(self, resource, predicates=None):

View file

@ -205,10 +205,33 @@ class IResource(ILoopsObject, IPotentialTarget):
"""
def getClients(relationships=None):
""" Return a sequence of objects that are clients of the resource,
i.e. that have some relation with it.
""" Return a sequence of objects that the resource is the target of.
"""
def getConcepts(predicates=None):
""" Return a tuple of concepts related to self as parent concepts,
optionally restricted to the predicates given.
"""
def getConceptRelations(predicates=None, concepts=None):
""" Return a sequence of relations to concepts assigned to self
as parent concepts, optionally restricted to the predicates given
or to a certain concept.
"""
def assignConcept(concept, predicate):
""" Assign an existing concept to self using the predicate given.
The assigned concept will be a parent concept of self.
The predicate defaults to the concept manager's default predicate.
"""
def deassignConcept(concept, predicates=None):
""" Remove the concept relations to the concept given from self,
optionally restricting them to the predicates given.
"""
class IDocumentSchema(IResourceSchema):

View file

@ -27,6 +27,7 @@ from zope.app.container.btree import BTreeContainer
from zope.app.container.contained import Contained
from zope.app.file.image import Image as BaseMediaAsset
from zope.component import adapts
from zope.i18nmessageid import MessageFactory
from zope.interface import implements
from persistent import Persistent
from cStringIO import StringIO
@ -39,12 +40,18 @@ from interfaces import IMediaAsset, IMediaAssetSchema, IMediaAssetView
from interfaces import IResourceManager, IResourceManagerContained
from interfaces import ILoopsContained
from interfaces import IIndexAttributes
from concept import ResourceRelation
from view import TargetRelation
_ = MessageFactory('loops')
class Resource(Contained, Persistent):
implements(IResource, IResourceManagerContained, IRelatable)
_size = _width = _height = 0
_title = u''
def getTitle(self): return self._title
def setTitle(self, title): self._title = title
@ -57,17 +64,34 @@ class Resource(Contained, Persistent):
def getContentType(self): return self._contentType
contentType = property(getContentType, setContentType)
def __init__(self, title=u''):
self.title = title
def getLoopsRoot(self):
return zapi.getParent(self).getLoopsRoot()
def getClients(self, relationships=None):
if relationships is None:
relationships = [TargetRelation]
rels = getRelations(second=self, relationships=relationships)
return [r.first for r in rels]
def __init__(self, title=u''):
self.title = title
# concept relations
_size = _width = _height = 0
def getConceptRelations (self, predicates=None, concept=None):
predicates = predicates is None and ['*'] or predicates
relationships = [ResourceRelation(None, self, p) for p in predicates]
# TODO: sort...
return getRelations(first=concept, second=self, relationships=relationships)
def getConcepts(self, predicates=None):
return [r.first for r in self.getConceptRelations(predicates)]
def assignConcept(self, concept, predicate=None):
concept.assignResource(self, predicate)
def deassignConcept(self, concept, predicates=None):
concept.deassignResource(self, predicates)
class Document(Resource):
@ -138,3 +162,14 @@ class IndexAttributes(object):
context = self.context
return ':'.join(('loops:resource', context.__class__.__name__))
def getResourceTypes():
return (('loops.resource.Document', _(u'Document')),
('loops.resource.MediaAsset', _(u'Media Asset')),
)
def getResourceTypesForSearch():
return (('loops:resource:Document', _(u'Document')),
('loops:resource:MediaAsset', _(u'Media Asset')),
)