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:
helmutm 2009-01-20 09:15:29 +00:00
parent b28c81d9b4
commit edd2b3fbe4
12 changed files with 232 additions and 76 deletions

View file

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

View file

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

View file

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

View file

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

View file

@ -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
View file

@ -0,0 +1,4 @@
"""
$Id$
"""

76
schema/base.py Normal file
View 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)

View file

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

View file

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

View file

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