diff --git a/browser/action.py b/browser/action.py index 4330f6e..20420d2 100644 --- a/browser/action.py +++ b/browser/action.py @@ -39,7 +39,7 @@ class TargetAction(Action): viewTitle = '' addParams = {} - #@Lazy + @Lazy def url(self): if self.page is None: # how could this happen? baseUrl = self.view.virtualTargetUrl diff --git a/browser/form.py b/browser/form.py index d68e74f..0e10db8 100644 --- a/browser/form.py +++ b/browser/form.py @@ -57,6 +57,7 @@ from loops.browser.concept import ConceptRelationView from loops.i18n.browser import I18NView from loops.query import ConceptQuery, IQueryConcept from loops.resource import Resource +from loops.schema.field import relation_macros from loops.type import ITypeConcept from loops import util from loops.util import _ @@ -113,6 +114,7 @@ class ObjectForm(NodeView): # replace HTML edit widget with Dojo Editor renderers['input_html'] = self.template.macros['input_html'] renderers['input_grid'] = grid_macros.macros['input_grid'] + renderers['input_relationset'] = relation_macros.macros['input_relationset'] return renderers @Lazy @@ -358,6 +360,7 @@ class CreateConceptForm(CreateObjectForm): if ti is None: return c ad = ti(c) + ad.__is_dummy__ = True return ad @Lazy @@ -394,7 +397,8 @@ class CreateConceptPage(CreateConceptForm): @Lazy def nextUrl(self): - return self.nodeView.getUrlForTarget(self.context) + #return self.nodeView.getUrlForTarget(self.context) + return self.getUrlForTarget(self.context) class InnerForm(CreateObjectForm): diff --git a/browser/loops.js b/browser/loops.js index 1345f4c..8b0b7f2 100644 --- a/browser/loops.js +++ b/browser/loops.js @@ -143,6 +143,17 @@ function addConceptAssignment(prefix, suffix) { node.appendChild(tr); } +function addRelation(fieldName) { + valuesNode = dojo.byId(fieldName + '_values'); + widget = dijit.byId(fieldName + '_search'); + token = widget.getValue(); + title = widget.getDisplayedValue(); + ih = ' ' + title + ''; + newNode = document.createElement('div'); + newNode.innerHTML = ih; + valuesNode.appendChild(newNode); +} + function validate() { //var form = dijit.byId('dialog_form'); var form = dojo.byId('dialog_form'); diff --git a/common.py b/common.py index 79ac503..5724308 100644 --- a/common.py +++ b/common.py @@ -96,6 +96,8 @@ class AdapterBase(object): _noexportAttributes = () _textIndexAttributes = () + __is_dummy__ = False + languageInfo = None def __init__(self, context): @@ -107,7 +109,7 @@ class AdapterBase(object): return getattr(self.context, '_' + attr, None) def __setattr__(self, attr, value): - if attr in self._adapterAttributes: + if attr.startswith('__') or attr in self._adapterAttributes: try: object.__setattr__(self, attr, value) except AttributeError: @@ -332,6 +334,8 @@ class ParentRelationSet(RelationSet): self.context.deassignParent(related, [self.predicate]) def __iter__(self): + if self.adapted.__is_dummy__: + return for c in self.context.getParents([self.predicate]): yield adapted(c, langInfo=self.langInfo) @@ -350,6 +354,8 @@ class ChildRelationSet(RelationSet): self.context.deassignChild(related, [self.predicate]) def __iter__(self): + if self.adapted.__is_dummy__: + return for c in self.context.getChildren([self.predicate]): yield adapted(c, langInfo=self.langInfo) diff --git a/configure.zcml b/configure.zcml index c05be78..fb088c1 100644 --- a/configure.zcml +++ b/configure.zcml @@ -346,13 +346,15 @@ name="fileupload" /> + - - + - - + + diff --git a/schema/__init__.py b/schema/__init__.py new file mode 100644 index 0000000..4bc90fb --- /dev/null +++ b/schema/__init__.py @@ -0,0 +1,4 @@ +""" +$Id$ +""" + diff --git a/schema/base.py b/schema/base.py new file mode 100644 index 0000000..d4711f7 --- /dev/null +++ b/schema/base.py @@ -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) + diff --git a/schema.py b/schema/factory.py similarity index 60% rename from schema.py rename to schema/factory.py index 1d47974..33697af 100644 --- a/schema.py +++ b/schema/factory.py @@ -17,64 +17,16 @@ # """ -Specialized fields and schema factories +Specialized fields factories. $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.factory import SchemaFactory 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): diff --git a/schema/field.py b/schema/field.py new file mode 100644 index 0000000..f799bc9 --- /dev/null +++ b/schema/field.py @@ -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 '' diff --git a/schema/relation_macros.pt b/schema/relation_macros.pt new file mode 100755 index 0000000..f0d1dda --- /dev/null +++ b/schema/relation_macros.pt @@ -0,0 +1,31 @@ + + + + + +
+
+
+
+ + +
+
+ +
+ + + + diff --git a/search/browser.py b/search/browser.py index 3579625..43996c6 100644 --- a/search/browser.py +++ b/search/browser.py @@ -98,29 +98,32 @@ class Search(BaseView): title = request.get('name') if title == '*': title = None - type = request.get('searchType') + types = request.get('searchType') data = [] - if title or type: + if title or types: if title is not None: title = title.replace('(', ' ').replace(')', ' ').replace(' -', ' ') #title = title.split(' ', 1)[0] - if not type: - type = 'loops:concept:*' - result = ConceptQuery(self).query(title=title or None, type=type, - exclude=('system',)) - for o in result: - if o.getLoopsRoot() == self.loopsRoot: - name = adapted(o, self.languageInfo).title - if title and title.endswith('*'): - title = title[:-1] - sort = ((title and name.startswith(title) and '0' or '1') - + name.lower()) - if o.conceptType is None: - raise ValueError('Concept Type missing for %r.' % name) - data.append({'label': '%s (%s)' % (name, o.conceptType.title), - 'name': name, - 'id': util.getUidForObject(o), - 'sort': sort}) + if not types: + types = ['loops:concept:*'] + if not isinstance(types, (list, tuple)): + types = [types] + for type in types: + result = ConceptQuery(self).query(title=title or None, type=type, + exclude=('system',)) + for o in result: + if o.getLoopsRoot() == self.loopsRoot: + name = adapted(o, self.languageInfo).title + if title and title.endswith('*'): + title = title[:-1] + sort = ((title and name.startswith(title) and '0' or '1') + + name.lower()) + if o.conceptType is None: + raise ValueError('Concept Type missing for %r.' % name) + data.append({'label': '%s (%s)' % (name, o.conceptType.title), + 'name': name, + 'id': util.getUidForObject(o), + 'sort': sort}) data.sort(key=lambda x: x['sort']) if not title: data.insert(0, {'label': '', 'name': '', 'id': ''}) diff --git a/tests/setup.py b/tests/setup.py index 7101e78..d830da4 100644 --- a/tests/setup.py +++ b/tests/setup.py @@ -61,7 +61,9 @@ from loops.expert.concept import QueryConcept from loops.resource import Resource, FileAdapter, TextDocumentAdapter from loops.resource import Document, MediaAsset 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 zope.security.management import setSecurityPolicy from loops.security.policy import LoopsSecurityPolicy @@ -141,6 +143,7 @@ class TestSite(object): component.provideAdapter(BooleanFieldInstance, name='boolean') component.provideAdapter(ListFieldInstance, name='list') component.provideAdapter(FileUploadFieldInstance, name='fileupload') + component.provideAdapter(RelationSetFieldInstance, name='relationset') component.provideAdapter(SchemaFactory) component.provideAdapter(ResourceSchemaFactory) component.provideAdapter(FileSchemaFactory)