work in progress: relation fields/widgets
git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@3165 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
parent
b28c81d9b4
commit
edd2b3fbe4
12 changed files with 232 additions and 76 deletions
|
@ -39,7 +39,7 @@ class TargetAction(Action):
|
||||||
viewTitle = ''
|
viewTitle = ''
|
||||||
addParams = {}
|
addParams = {}
|
||||||
|
|
||||||
#@Lazy
|
@Lazy
|
||||||
def url(self):
|
def url(self):
|
||||||
if self.page is None: # how could this happen?
|
if self.page is None: # how could this happen?
|
||||||
baseUrl = self.view.virtualTargetUrl
|
baseUrl = self.view.virtualTargetUrl
|
||||||
|
|
|
@ -57,6 +57,7 @@ from loops.browser.concept import ConceptRelationView
|
||||||
from loops.i18n.browser import I18NView
|
from loops.i18n.browser import I18NView
|
||||||
from loops.query import ConceptQuery, IQueryConcept
|
from loops.query import ConceptQuery, IQueryConcept
|
||||||
from loops.resource import Resource
|
from loops.resource import Resource
|
||||||
|
from loops.schema.field import relation_macros
|
||||||
from loops.type import ITypeConcept
|
from loops.type import ITypeConcept
|
||||||
from loops import util
|
from loops import util
|
||||||
from loops.util import _
|
from loops.util import _
|
||||||
|
@ -113,6 +114,7 @@ class ObjectForm(NodeView):
|
||||||
# replace HTML edit widget with Dojo Editor
|
# replace HTML edit widget with Dojo Editor
|
||||||
renderers['input_html'] = self.template.macros['input_html']
|
renderers['input_html'] = self.template.macros['input_html']
|
||||||
renderers['input_grid'] = grid_macros.macros['input_grid']
|
renderers['input_grid'] = grid_macros.macros['input_grid']
|
||||||
|
renderers['input_relationset'] = relation_macros.macros['input_relationset']
|
||||||
return renderers
|
return renderers
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
|
@ -358,6 +360,7 @@ class CreateConceptForm(CreateObjectForm):
|
||||||
if ti is None:
|
if ti is None:
|
||||||
return c
|
return c
|
||||||
ad = ti(c)
|
ad = ti(c)
|
||||||
|
ad.__is_dummy__ = True
|
||||||
return ad
|
return ad
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
|
@ -394,7 +397,8 @@ class CreateConceptPage(CreateConceptForm):
|
||||||
|
|
||||||
@Lazy
|
@Lazy
|
||||||
def nextUrl(self):
|
def nextUrl(self):
|
||||||
return self.nodeView.getUrlForTarget(self.context)
|
#return self.nodeView.getUrlForTarget(self.context)
|
||||||
|
return self.getUrlForTarget(self.context)
|
||||||
|
|
||||||
|
|
||||||
class InnerForm(CreateObjectForm):
|
class InnerForm(CreateObjectForm):
|
||||||
|
|
|
@ -143,6 +143,17 @@ function addConceptAssignment(prefix, suffix) {
|
||||||
node.appendChild(tr);
|
node.appendChild(tr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addRelation(fieldName) {
|
||||||
|
valuesNode = dojo.byId(fieldName + '_values');
|
||||||
|
widget = dijit.byId(fieldName + '_search');
|
||||||
|
token = widget.getValue();
|
||||||
|
title = widget.getDisplayedValue();
|
||||||
|
ih = '<input type="checkbox" name="' + name + ':list" value="' + token + '" checked> <span>' + title + '</span>';
|
||||||
|
newNode = document.createElement('div');
|
||||||
|
newNode.innerHTML = ih;
|
||||||
|
valuesNode.appendChild(newNode);
|
||||||
|
}
|
||||||
|
|
||||||
function validate() {
|
function validate() {
|
||||||
//var form = dijit.byId('dialog_form');
|
//var form = dijit.byId('dialog_form');
|
||||||
var form = dojo.byId('dialog_form');
|
var form = dojo.byId('dialog_form');
|
||||||
|
|
|
@ -96,6 +96,8 @@ class AdapterBase(object):
|
||||||
_noexportAttributes = ()
|
_noexportAttributes = ()
|
||||||
_textIndexAttributes = ()
|
_textIndexAttributes = ()
|
||||||
|
|
||||||
|
__is_dummy__ = False
|
||||||
|
|
||||||
languageInfo = None
|
languageInfo = None
|
||||||
|
|
||||||
def __init__(self, context):
|
def __init__(self, context):
|
||||||
|
@ -107,7 +109,7 @@ class AdapterBase(object):
|
||||||
return getattr(self.context, '_' + attr, None)
|
return getattr(self.context, '_' + attr, None)
|
||||||
|
|
||||||
def __setattr__(self, attr, value):
|
def __setattr__(self, attr, value):
|
||||||
if attr in self._adapterAttributes:
|
if attr.startswith('__') or attr in self._adapterAttributes:
|
||||||
try:
|
try:
|
||||||
object.__setattr__(self, attr, value)
|
object.__setattr__(self, attr, value)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
|
@ -332,6 +334,8 @@ class ParentRelationSet(RelationSet):
|
||||||
self.context.deassignParent(related, [self.predicate])
|
self.context.deassignParent(related, [self.predicate])
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
|
if self.adapted.__is_dummy__:
|
||||||
|
return
|
||||||
for c in self.context.getParents([self.predicate]):
|
for c in self.context.getParents([self.predicate]):
|
||||||
yield adapted(c, langInfo=self.langInfo)
|
yield adapted(c, langInfo=self.langInfo)
|
||||||
|
|
||||||
|
@ -350,6 +354,8 @@ class ChildRelationSet(RelationSet):
|
||||||
self.context.deassignChild(related, [self.predicate])
|
self.context.deassignChild(related, [self.predicate])
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
|
if self.adapted.__is_dummy__:
|
||||||
|
return
|
||||||
for c in self.context.getChildren([self.predicate]):
|
for c in self.context.getChildren([self.predicate]):
|
||||||
yield adapted(c, langInfo=self.langInfo)
|
yield adapted(c, langInfo=self.langInfo)
|
||||||
|
|
||||||
|
|
|
@ -346,13 +346,15 @@
|
||||||
name="fileupload" />
|
name="fileupload" />
|
||||||
<adapter factory="cybertools.composer.schema.grid.field.GridFieldInstance"
|
<adapter factory="cybertools.composer.schema.grid.field.GridFieldInstance"
|
||||||
name="grid" />
|
name="grid" />
|
||||||
|
<adapter factory="loops.schema.field.RelationSetFieldInstance"
|
||||||
|
name="relationset" />
|
||||||
|
|
||||||
<adapter factory="cybertools.composer.schema.factory.SchemaFactory" />
|
<adapter factory="cybertools.composer.schema.factory.SchemaFactory" />
|
||||||
<adapter factory="loops.schema.ResourceSchemaFactory" />
|
<adapter factory="loops.schema.factory.ResourceSchemaFactory" />
|
||||||
<adapter factory="loops.schema.ResourceSchemaFactory"
|
<adapter factory="loops.schema.factory.ResourceSchemaFactory"
|
||||||
for="loops.interfaces.IResource" />
|
for="loops.interfaces.IResource" />
|
||||||
<adapter factory="loops.schema.FileSchemaFactory" />
|
<adapter factory="loops.schema.factory.FileSchemaFactory" />
|
||||||
<adapter factory="loops.schema.NoteSchemaFactory" />
|
<adapter factory="loops.schema.factory.NoteSchemaFactory" />
|
||||||
|
|
||||||
<adapter factory="loops.setup.SetupManager" />
|
<adapter factory="loops.setup.SetupManager" />
|
||||||
|
|
||||||
|
|
4
schema/__init__.py
Normal file
4
schema/__init__.py
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
"""
|
||||||
|
$Id$
|
||||||
|
"""
|
||||||
|
|
76
schema/base.py
Normal file
76
schema/base.py
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
#
|
||||||
|
# Copyright (c) 2009 Helmut Merz helmutm@cy55.de
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
Specialized field definitions.
|
||||||
|
|
||||||
|
$Id$
|
||||||
|
"""
|
||||||
|
|
||||||
|
from zope.component import adapts
|
||||||
|
from zope.interface import Attribute, implements
|
||||||
|
from zope.schema import Choice, List
|
||||||
|
from zope.schema.interfaces import IChoice, IList
|
||||||
|
|
||||||
|
from cybertools.composer.schema.interfaces import FieldType
|
||||||
|
|
||||||
|
|
||||||
|
class IRelation(IChoice):
|
||||||
|
""" An object addressed via a single relation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
target_types = Attribute('A list of names that denote types of '
|
||||||
|
'loops objects (typically concept types) that may be used as '
|
||||||
|
'targets for the relation.')
|
||||||
|
|
||||||
|
|
||||||
|
class IRelationSet(IList):
|
||||||
|
""" A collection of objects addressed via a set of relations.
|
||||||
|
|
||||||
|
Despite its name, the collection may have a predefined order.
|
||||||
|
"""
|
||||||
|
|
||||||
|
target_types = Attribute('A list of names that denote types of '
|
||||||
|
'loops objects (typically concept types) that may be used as '
|
||||||
|
'targets for the relations.')
|
||||||
|
|
||||||
|
|
||||||
|
class Relation(Choice):
|
||||||
|
|
||||||
|
implements(IRelation)
|
||||||
|
|
||||||
|
__typeInfo__ = ('relation',)
|
||||||
|
|
||||||
|
def __init__(self, *args, **kw):
|
||||||
|
self.target_types = kw.pop('target_types')
|
||||||
|
super(Relation, self).__init__(*args, **kw)
|
||||||
|
|
||||||
|
|
||||||
|
class RelationSet(List):
|
||||||
|
|
||||||
|
implements(IRelationSet)
|
||||||
|
|
||||||
|
__typeInfo__ = ('relationset',
|
||||||
|
FieldType('relationset', 'relationset',
|
||||||
|
u'A field representing a sequence of related objects.',
|
||||||
|
instanceName='relationset'))
|
||||||
|
|
||||||
|
def __init__(self, *args, **kw):
|
||||||
|
self.target_types = kw.pop('target_types')
|
||||||
|
super(RelationSet, self).__init__(*args, **kw)
|
||||||
|
|
|
@ -17,64 +17,16 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Specialized fields and schema factories
|
Specialized fields factories.
|
||||||
|
|
||||||
$Id$
|
$Id$
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from zope.component import adapts
|
from zope.component import adapts
|
||||||
from zope.interface import Attribute, implements
|
|
||||||
from zope.schema import Choice, List
|
|
||||||
from zope.schema.interfaces import IChoice, IList
|
|
||||||
|
|
||||||
from cybertools.composer.schema.factory import SchemaFactory
|
from cybertools.composer.schema.factory import SchemaFactory
|
||||||
from loops.interfaces import IResourceAdapter, IFile, INote
|
from loops.interfaces import IResourceAdapter, IFile, INote
|
||||||
|
|
||||||
# fields
|
|
||||||
|
|
||||||
class IRelation(IChoice):
|
|
||||||
""" An object addressed via a single relation.
|
|
||||||
"""
|
|
||||||
|
|
||||||
target_types = Attribute('A list of names that denote types of '
|
|
||||||
'loops objects (typically concept types) that may be used as '
|
|
||||||
'targets for the relation.')
|
|
||||||
|
|
||||||
|
|
||||||
class IRelationSet(IList):
|
|
||||||
""" A collection of objects addressed via a set of relations.
|
|
||||||
|
|
||||||
Despite its name, the collection may have a predefined order.
|
|
||||||
"""
|
|
||||||
|
|
||||||
target_types = Attribute('A list of names that denote types of '
|
|
||||||
'loops objects (typically concept types) that may be used as '
|
|
||||||
'targets for the relations.')
|
|
||||||
|
|
||||||
|
|
||||||
class Relation(Choice):
|
|
||||||
|
|
||||||
implements(IRelation)
|
|
||||||
|
|
||||||
__typeInfo = ('relation',)
|
|
||||||
|
|
||||||
def __init__(self, *args, **kw):
|
|
||||||
self.target_types = kw.pop('target_types')
|
|
||||||
super(Relation, self).__init__(*args, **kw)
|
|
||||||
|
|
||||||
|
|
||||||
class RelationSet(List):
|
|
||||||
|
|
||||||
implements(IRelationSet)
|
|
||||||
|
|
||||||
__typeInfo = ('relationset',)
|
|
||||||
|
|
||||||
def __init__(self, *args, **kw):
|
|
||||||
self.target_types = kw.pop('target_types')
|
|
||||||
super(RelationSet, self).__init__(*args, **kw)
|
|
||||||
|
|
||||||
|
|
||||||
# schema factories
|
|
||||||
|
|
||||||
class ResourceSchemaFactory(SchemaFactory):
|
class ResourceSchemaFactory(SchemaFactory):
|
||||||
|
|
64
schema/field.py
Normal file
64
schema/field.py
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
#
|
||||||
|
# Copyright (c) 2008 Helmut Merz helmutm@cy55.de
|
||||||
|
#
|
||||||
|
# This program is free software; you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation; either version 2 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program; if not, write to the Free Software
|
||||||
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
Field and field instance classes for grids.
|
||||||
|
|
||||||
|
$Id$
|
||||||
|
"""
|
||||||
|
|
||||||
|
from zope import component
|
||||||
|
from zope.component import adapts
|
||||||
|
from zope.interface import implements
|
||||||
|
from zope.app.pagetemplate import ViewPageTemplateFile
|
||||||
|
from zope.cachedescriptors.property import Lazy
|
||||||
|
import zope.schema
|
||||||
|
|
||||||
|
from cybertools.composer.schema.factory import createField
|
||||||
|
from cybertools.composer.schema.field import ListFieldInstance
|
||||||
|
from cybertools.composer.schema.interfaces import IField, IFieldInstance
|
||||||
|
from cybertools.composer.schema.interfaces import fieldTypes, undefined
|
||||||
|
from cybertools.util.format import toStr, toUnicode
|
||||||
|
from cybertools.util import json
|
||||||
|
from loops import util
|
||||||
|
|
||||||
|
|
||||||
|
relation_macros = ViewPageTemplateFile('relation_macros.pt')
|
||||||
|
|
||||||
|
|
||||||
|
class RelationSetFieldInstance(ListFieldInstance):
|
||||||
|
|
||||||
|
def marshall(self, value):
|
||||||
|
return [dict(title=v.title, uid=util.getUidForObject(v.context))
|
||||||
|
for v in value]
|
||||||
|
|
||||||
|
def display(self, value):
|
||||||
|
return value
|
||||||
|
|
||||||
|
def unmarshall(self, value):
|
||||||
|
return value
|
||||||
|
|
||||||
|
@Lazy
|
||||||
|
def typesParams(self):
|
||||||
|
result = []
|
||||||
|
types = self.context.target_types
|
||||||
|
for t in types:
|
||||||
|
result.append('searchType=loops:concept:%s' % t)
|
||||||
|
if result:
|
||||||
|
return '?' + '&'.join(result)
|
||||||
|
return ''
|
31
schema/relation_macros.pt
Executable file
31
schema/relation_macros.pt
Executable file
|
@ -0,0 +1,31 @@
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US"
|
||||||
|
i18n:domain="loops">
|
||||||
|
<body>
|
||||||
|
|
||||||
|
|
||||||
|
<metal:textline define-macro="input_relationset"
|
||||||
|
tal:define="fieldInstance field/getFieldInstance;
|
||||||
|
types fieldInstance/typesParams">
|
||||||
|
<div dojoType="dojox.data.QueryReadStore" jsId="conceptSearch"
|
||||||
|
tal:attributes="url string:listConceptsForComboBox.js$types;
|
||||||
|
jsId string:${name}_search_store" >
|
||||||
|
</div>
|
||||||
|
<div tal:attributes="id string:${name}_values">
|
||||||
|
<div tal:repeat="obj data/?name">
|
||||||
|
<input type="checkbox" checked
|
||||||
|
tal:attributes="name string:$name:list;
|
||||||
|
value obj/uid" />
|
||||||
|
<span tal:content="obj/title" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<input dojoType="dijit.form.FilteringSelect"
|
||||||
|
autoComplete="False" labelAttr="label"
|
||||||
|
tal:attributes="store string:${name}_search_store;
|
||||||
|
name string:${name}_search;
|
||||||
|
id string:${name}_search;
|
||||||
|
onChange string:addRelation('$name')" />
|
||||||
|
</metal:textline>
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -98,29 +98,32 @@ class Search(BaseView):
|
||||||
title = request.get('name')
|
title = request.get('name')
|
||||||
if title == '*':
|
if title == '*':
|
||||||
title = None
|
title = None
|
||||||
type = request.get('searchType')
|
types = request.get('searchType')
|
||||||
data = []
|
data = []
|
||||||
if title or type:
|
if title or types:
|
||||||
if title is not None:
|
if title is not None:
|
||||||
title = title.replace('(', ' ').replace(')', ' ').replace(' -', ' ')
|
title = title.replace('(', ' ').replace(')', ' ').replace(' -', ' ')
|
||||||
#title = title.split(' ', 1)[0]
|
#title = title.split(' ', 1)[0]
|
||||||
if not type:
|
if not types:
|
||||||
type = 'loops:concept:*'
|
types = ['loops:concept:*']
|
||||||
result = ConceptQuery(self).query(title=title or None, type=type,
|
if not isinstance(types, (list, tuple)):
|
||||||
exclude=('system',))
|
types = [types]
|
||||||
for o in result:
|
for type in types:
|
||||||
if o.getLoopsRoot() == self.loopsRoot:
|
result = ConceptQuery(self).query(title=title or None, type=type,
|
||||||
name = adapted(o, self.languageInfo).title
|
exclude=('system',))
|
||||||
if title and title.endswith('*'):
|
for o in result:
|
||||||
title = title[:-1]
|
if o.getLoopsRoot() == self.loopsRoot:
|
||||||
sort = ((title and name.startswith(title) and '0' or '1')
|
name = adapted(o, self.languageInfo).title
|
||||||
+ name.lower())
|
if title and title.endswith('*'):
|
||||||
if o.conceptType is None:
|
title = title[:-1]
|
||||||
raise ValueError('Concept Type missing for %r.' % name)
|
sort = ((title and name.startswith(title) and '0' or '1')
|
||||||
data.append({'label': '%s (%s)' % (name, o.conceptType.title),
|
+ name.lower())
|
||||||
'name': name,
|
if o.conceptType is None:
|
||||||
'id': util.getUidForObject(o),
|
raise ValueError('Concept Type missing for %r.' % name)
|
||||||
'sort': sort})
|
data.append({'label': '%s (%s)' % (name, o.conceptType.title),
|
||||||
|
'name': name,
|
||||||
|
'id': util.getUidForObject(o),
|
||||||
|
'sort': sort})
|
||||||
data.sort(key=lambda x: x['sort'])
|
data.sort(key=lambda x: x['sort'])
|
||||||
if not title:
|
if not title:
|
||||||
data.insert(0, {'label': '', 'name': '', 'id': ''})
|
data.insert(0, {'label': '', 'name': '', 'id': ''})
|
||||||
|
|
|
@ -61,7 +61,9 @@ from loops.expert.concept import QueryConcept
|
||||||
from loops.resource import Resource, FileAdapter, TextDocumentAdapter
|
from loops.resource import Resource, FileAdapter, TextDocumentAdapter
|
||||||
from loops.resource import Document, MediaAsset
|
from loops.resource import Document, MediaAsset
|
||||||
from loops.resource import IndexAttributes as ResourceIndexAttributes
|
from loops.resource import IndexAttributes as ResourceIndexAttributes
|
||||||
from loops.schema import ResourceSchemaFactory, FileSchemaFactory, NoteSchemaFactory
|
from loops.schema.factory import ResourceSchemaFactory, FileSchemaFactory, \
|
||||||
|
NoteSchemaFactory
|
||||||
|
from loops.schema.field import RelationSetFieldInstance
|
||||||
from loops.security.common import grantAcquiredSecurity, revokeAcquiredSecurity
|
from loops.security.common import grantAcquiredSecurity, revokeAcquiredSecurity
|
||||||
from zope.security.management import setSecurityPolicy
|
from zope.security.management import setSecurityPolicy
|
||||||
from loops.security.policy import LoopsSecurityPolicy
|
from loops.security.policy import LoopsSecurityPolicy
|
||||||
|
@ -141,6 +143,7 @@ class TestSite(object):
|
||||||
component.provideAdapter(BooleanFieldInstance, name='boolean')
|
component.provideAdapter(BooleanFieldInstance, name='boolean')
|
||||||
component.provideAdapter(ListFieldInstance, name='list')
|
component.provideAdapter(ListFieldInstance, name='list')
|
||||||
component.provideAdapter(FileUploadFieldInstance, name='fileupload')
|
component.provideAdapter(FileUploadFieldInstance, name='fileupload')
|
||||||
|
component.provideAdapter(RelationSetFieldInstance, name='relationset')
|
||||||
component.provideAdapter(SchemaFactory)
|
component.provideAdapter(SchemaFactory)
|
||||||
component.provideAdapter(ResourceSchemaFactory)
|
component.provideAdapter(ResourceSchemaFactory)
|
||||||
component.provideAdapter(FileSchemaFactory)
|
component.provideAdapter(FileSchemaFactory)
|
||||||
|
|
Loading…
Add table
Reference in a new issue