diff --git a/README.txt b/README.txt index 9338387..9201734 100755 --- a/README.txt +++ b/README.txt @@ -37,7 +37,7 @@ top-level loops container and a concept manager: >>> #sorted(concepts) >>> #sorted(resources) >>> len(concepts) + len(resources) - 14 + 13 >>> loopsRoot = site['loops'] @@ -121,7 +121,7 @@ type manager. >>> from loops.concept import ConceptTypeSourceList >>> types = ConceptTypeSourceList(cc1) >>> sorted(t.title for t in types) - [u'Customer', u'Domain', u'Predicate', u'Query', u'Topic', u'Type', u'Unknown Type'] + [u'Customer', u'Domain', u'Predicate', u'Topic', u'Type', u'Unknown Type'] Using a PredicateSourceList we can retrieve a list of the available predicates. @@ -197,7 +197,6 @@ types and predicates. [(u'Customer', '.loops/concepts/customer'), (u'Domain', '.loops/concepts/domain'), (u'Predicate', '.loops/concepts/predicate'), - (u'Query', '.loops/concepts/query'), (u'Topic', '.loops/concepts/topic'), (u'Type', '.loops/concepts/type'), (u'Unknown Type', '.loops/concepts/unknown')] @@ -487,7 +486,7 @@ view; these views we have to provide as multi-adapters: >>> view = ConfigureView(m111, TestRequest(form = form)) >>> tt = view.targetTypes() >>> len(tt) - 10 + 9 >>> sorted((t.token, t.title) for t in view.targetTypes())[1] ('.loops/concepts/domain', u'Domain') >>> view.update() @@ -580,7 +579,7 @@ is ``NodeView.getUrlForTarget()`` that expects a ConceptView, ResourceView, or ConceptRelationView as its argument. >>> view.getUrlForTarget(childRels[0]) - 'http://127.0.0.1/loops/views/m1/m11/m112/.target39' + 'http://127.0.0.1/loops/views/m1/m11/m112/.target37' Actions ------- diff --git a/browser/concept_macros.pt b/browser/concept_macros.pt index 396f7e6..7dfa370 100644 --- a/browser/concept_macros.pt +++ b/browser/concept_macros.pt @@ -121,9 +121,10 @@ tal:define="resources python: list(item.resources())" tal:condition="resources">

Resources

- +
- + - +
   Title Type
+ + + + diff --git a/configure.zcml b/configure.zcml index 5e04180..3c7541f 100644 --- a/configure.zcml +++ b/configure.zcml @@ -436,6 +436,7 @@ + diff --git a/data/loops_std.dmp b/data/loops_std.dmp index 5477d9c..2cd4bc2 100644 --- a/data/loops_std.dmp +++ b/data/loops_std.dmp @@ -9,7 +9,7 @@ type(u'note', u'Note', options=u'', typeInterface='loops.interfaces.INote', view type(u'person', u'Person', options=u'', typeInterface='loops.knowledge.interfaces.IPerson', viewName=u'') type(u'predicate', u'Predicate', options=u'', typeInterface=u'', viewName=u'') type(u'process', u'Prozess', options=u'', typeInterface=u'', viewName=u'') -type(u'query', u'Query', options=u'', typeInterface='loops.query.IQueryConcept', viewName=u'') +type(u'query', u'Query', options=u'', typeInterface='loops.expert.concept.IQueryConcept', viewName=u'') type(u'textdocument', u'Text', options=u'', typeInterface='loops.interfaces.ITextDocument', viewName=u'') type(u'topic', u'Topic', options=u'', typeInterface='loops.knowledge.interfaces.ITopic', viewName=u'') type(u'type', u'Type', options=u'', typeInterface='loops.interfaces.ITypeConcept', viewName=u'') diff --git a/expert/README.txt b/expert/README.txt index 9c7eb7f..257f29b 100644 --- a/expert/README.txt +++ b/expert/README.txt @@ -27,7 +27,7 @@ configuration): >>> concepts, resources, views = t.setup() >>> len(concepts) + len(resources) - 32 + 33 >>> loopsRoot = site['loops'] @@ -47,19 +47,19 @@ Type- and text-based queries >>> from loops.expert import query >>> qu = query.Title('ty*') >>> list(qu.apply()) - [0, 1, 45] + [0, 1, 47] >>> qu = query.Type('loops:*') >>> len(list(qu.apply())) - 32 + 33 >>> qu = query.Type('loops:concept:predicate') >>> len(list(qu.apply())) - 6 + 7 >>> qu = query.Type('loops:concept:predicate') & query.Title('t*') >>> list(qu.apply()) - [1] + [1, 43] State-based queries ------------------- @@ -99,7 +99,7 @@ they have at least one concept assigned). >>> qu = query.State('classification_quality', 'classified') >>> list(qu.apply()) - [23, 25, 27] + [21, 23, 25] Using the stateful adapter for a resource we now manually execute the ``verify`` transition. @@ -113,16 +113,16 @@ Now only two resources are still in the ``qualified`` state, the changed one being in the ``verified`` state. >>> list(qu.apply()) - [25, 27] + [23, 25] >>> qu = query.State('classification_quality', 'verified') >>> list(qu.apply()) - [23] + [21] We may also provide a sequence of states for querying. >>> qu = query.State('classification_quality', ('classified', 'verified',)) >>> list(qu.apply()) - [23, 25, 27] + [21, 23, 25] Relationship-based queries -------------------------- @@ -135,7 +135,7 @@ syntax (that in turn is based on hurry.query). >>> cust1 = concepts['cust1'] >>> qu = query.Resources(cust1) >>> list(qu.apply()) - [23, 27] + [21, 25] Getting objects --------------- @@ -208,6 +208,15 @@ A query instance consists of >>> #qi.apply() +Query Concepts and Query Views +============================== + + >>> from loops.expert.concept import QueryConcept + >>> component.provideAdapter(QueryConcept) + + >>> from loops.expert.browser.base import BaseQueryView + + Fin de partie ============= diff --git a/expert/browser/base.py b/expert/browser/base.py index afb4e28..3f119c9 100644 --- a/expert/browser/base.py +++ b/expert/browser/base.py @@ -26,8 +26,12 @@ $Id$ from zope import interface, component from zope.app.pagetemplate import ViewPageTemplateFile from zope.cachedescriptors.property import Lazy +from zope.traversing.api import getName, getParent -from loops.browser.common import BaseView +from cybertools.browser.form import FormController +from loops.browser.common import BaseView, concept_macros +from loops.browser.concept import ConceptView +from loops.browser.resource import ResourceView, ResourceRelationView from loops.common import adapted from loops import util from loops.util import _ @@ -36,11 +40,65 @@ from loops.util import _ queryTemplate = ViewPageTemplateFile('query.pt') +#class BaseQueryView(ConceptView): class BaseQueryView(BaseView): template = queryTemplate + childViewFactory = ResourceRelationView + showCheckboxes = True + form_action = 'execute_query_action' @Lazy def macro(self): - return template.macros['query'] + return self.template.macros['query'] + @property + def infos(self): + return concept_macros.macros + + @property + def listings(self): + return concept_macros.macros + + @property + def listings(self): + return concept_macros.macros + + @Lazy + def targetPredicate(self): + return self.conceptManager['querytarget'] + + @Lazy + def defaultPredicate(self): + return self.conceptManager.getDefaultPredicate() + + @Lazy + def targets(self): + return self.context.getChildren([self.targetPredicate]) + + def queryInfo(self): + targetNames = ', '.join(["'%s'" % t.title for t in self.targets]) + return _(u'Selection using: $targets', + mapping=dict(targets=targetNames)) + + def results(self): + for t in self.targets: + for r in t.getResourceRelations([self.defaultPredicate]): + yield self.childViewFactory(r, self.request, contextIsSecond=True) + + +class ActionExecutor(FormController): + + def update(self): + form = self.request.form + actions = [k for k in form.keys() if k.startswith('action.')] + if actions: + uids = form.get('selection', []) + action = actions[0] + if action == 'action.delete': + print '*** delete', uids + for uid in uids: + obj = util.getObjectForUid(uid) + parent = getParent(obj) + del parent[getName(obj)] + return True diff --git a/expert/browser/configure.zcml b/expert/browser/configure.zcml index a74e4b7..97150b6 100644 --- a/expert/browser/configure.zcml +++ b/expert/browser/configure.zcml @@ -6,7 +6,7 @@ i18n_domain="loops"> -
-

- Query -

+
+
+ + +
+
+
+ +
+
diff --git a/expert/concept.py b/expert/concept.py new file mode 100644 index 0000000..5b905dd --- /dev/null +++ b/expert/concept.py @@ -0,0 +1,211 @@ +# +# 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 +# + +""" +Query concepts management stuff. + +$Id$ +""" + +from BTrees.IOBTree import IOBTree +from BTrees.IFBTree import weightedIntersection, weightedUnion, IFBucket +from zope import schema, component +from zope.interface import Interface, Attribute, implements +from zope.app.catalog.interfaces import ICatalog +from zope.app.intid.interfaces import IIntIds +from zope.cachedescriptors.property import Lazy + +from cybertools.typology.interfaces import IType +from loops.common import AdapterBase +from loops.interfaces import IConcept, IConceptSchema +from loops.security.common import canListObject +from loops.type import TypeInterfaceSourceList +from loops.versioning.util import getVersion +from loops import util +from loops.util import _ + + +class ScoredSet(set): + + def __init__(self, data=set(), scores={}): + super(ScoredSet, self).__init__(data) + self.scores = scores + + def getScore(self, obj): + return self.scores.get(obj, -1) + + +class BaseQuery(object): + + # TODO: replace with proper use of loops.expert.query + + def __init__(self, context): + self.context = context + + @Lazy + def catalog(self): + return component.getUtility(ICatalog) + + @Lazy + def loopsRoot(self): + return self.context.context.getLoopsRoot() + + def queryConcepts(self, title=None, type=None, **kw): + if type.endswith('*'): + start = type[:-1] + end = start + '\x7f' + else: + start = end = type + cat = self.catalog + if title: + result = cat.searchResults(loops_type=(start, end), loops_title=title) + else: + result = cat.searchResults(loops_type=(start, end)) + result = set(r for r in result if r.getLoopsRoot() == self.loopsRoot + and canListObject(r)) + if 'exclude' in kw: + r1 = set() + for r in result: + qur = IType(r).qualifiers + if not [qux for qux in kw['exclude'] if qux in qur]: + r1.add(r) + result = r1 + return result + + def queryConceptsWithChildren(self, title=None, type=None, uid=None, **kw): + if title: # there are a few characters that the index doesn't like + title = title.replace('(', ' ').replace(')', ' ') + if not title and not uid and (type is None or '*' in type): + return None + result = set() + if not uid: + queue = list(self.queryConcepts(title=title, type=type, **kw)) + else: + queue = [util.getObjectForUid(uid)] + concepts = [] + while queue: + c = queue.pop(0) + concepts.append(c) + for child in c.getChildren(): + # TODO: check for tree level, use relevance factors, ... + if child not in queue and child not in concepts: + queue.append(child) + for c in concepts: + result.add(c) + result.update(getVersion(r) for r in c.getResources()) + return result + + +class FullQuery(BaseQuery): + + def query(self, text=None, type=None, useTitle=True, useFull=False, + conceptTitle=None, conceptUid=None, conceptType=None, **kw): + result = set() + scores = {} + intids = component.getUtility(IIntIds) + rc = self.queryConceptsWithChildren(title=conceptTitle, uid=conceptUid, + type=conceptType) + if not rc and not text and '*' in type: # there should be some sort of selection... + return ScoredSet(result, scores) + if text or type != 'loops:*': # TODO: this may be highly inefficient! + cat = self.catalog + if type.endswith('*'): + start = type[:-1] + end = start + '\x7f' + else: + start = end = type + criteria = {'loops_type': (start, end),} + if useFull and text: + criteria['loops_text'] = text + r1 = cat.apply(criteria) #r1 = set(cat.searchResults(**criteria)) + else: + r1 = IFBucket() #r1 = set() + if useTitle and text: + if 'loops_text' in criteria: + del criteria['loops_text'] + criteria['loops_title'] = text + r2 = cat.apply(criteria) #r2 = set(cat.searchResults(**criteria)) + else: + r2 = IFBucket() #r2 = set() + if not r1 and not r2: + r1 = cat.apply(criteria) # search only for type + x, uids = weightedUnion(r1, r2) #result = r1.union(r2) + for r, score in uids.items(): + obj = intids.getObject(r) + result.add(obj) + scores[obj] = score + if rc is not None: + if result: + result = result.intersection(rc) + else: + result = rc + result = set(r for r in result + if r.getLoopsRoot() == self.loopsRoot + and canListObject(r) + and getVersion(r) == r) + return ScoredSet(result, scores) + + +class ConceptQuery(BaseQuery): + """ Find concepts of type `type` whose title starts with `title`. + """ + + def query(self, title=None, type=None, **kw): + if title and not title.endswith('*'): + title += '*' + return self.queryConcepts(title=title, type=type, **kw) + + +# QueryConcept: concept objects that allow querying the database. + +class IQueryConcept(IConceptSchema): + """ The schema for the query type. + """ + + viewName = schema.TextLine( + title=_(u'Adapter/View name'), + description=_(u'The name of the (multi-) adapter (typically a view) ' + 'to be used for the query and for presenting ' + 'the results'), + default=u'', + required=True) + + options = schema.List( + title=_(u'Options'), + description=_(u'Additional settings.'), + value_type=schema.TextLine(), + default=[], + required=False) + + +class QueryConcept(AdapterBase): + + implements(IQueryConcept) + + _contextAttributes = AdapterBase._contextAttributes + ['viewName'] + _adapterAttributes = AdapterBase._adapterAttributes + ('options',) + + def getOptions(self): + return getattr(self.context, '_options', []) + def setOptions(self, value): + self.context._options = value + options = property(getOptions, setOptions) + + +TypeInterfaceSourceList.typeInterfaces += (IQueryConcept,) + diff --git a/expert/configure.zcml b/expert/configure.zcml new file mode 100644 index 0000000..dd24ee9 --- /dev/null +++ b/expert/configure.zcml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + diff --git a/expert/setup.py b/expert/setup.py new file mode 100644 index 0000000..b5989a0 --- /dev/null +++ b/expert/setup.py @@ -0,0 +1,42 @@ +# +# 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 +# + +""" +Automatic setup of a loops site for the expert package. + +$Id$ +""" + +from loops.concept import Concept +from loops.expert.concept import IQueryConcept +from loops.setup import SetupManager as BaseSetupManager + + +class SetupManager(BaseSetupManager): + + def setup(self): + concepts = self.context.getConceptManager() + type = concepts.getTypeConcept() + predicate = concepts['predicate'] + # type concepts: + queryConcept = self.addAndConfigureObject(concepts, Concept, 'query', + title=u'Query', conceptType=type, + typeInterface=IQueryConcept) + # predicates: + queryTarget = self.addObject(concepts, Concept, 'querytarget', + title=u'is Query Target', conceptType=predicate) diff --git a/expert/testsetup.py b/expert/testsetup.py index 1f8eaea..90b5075 100644 --- a/expert/testsetup.py +++ b/expert/testsetup.py @@ -10,6 +10,7 @@ from zope.app.catalog.interfaces import ICatalog from cybertools.typology.interfaces import IType from loops import util from loops.concept import Concept +from loops.expert.setup import SetupManager as ExpertSetupManager from loops.resource import Resource from loops.knowledge.interfaces import IPerson from loops.knowledge.knowledge import Person @@ -32,6 +33,7 @@ class TestSite(BaseTestSite): component.provideAdapter(Person, provides=IPerson) component.provideAdapter(KnowledgeSetupManager, name='knowledge') + component.provideAdapter(ExpertSetupManager, name='expert') setup = SetupManager(loopsRoot) concepts, resources, views = setup.setup() diff --git a/external/README.txt b/external/README.txt index 6577a3a..78bc117 100644 --- a/external/README.txt +++ b/external/README.txt @@ -12,12 +12,12 @@ Let's set up a loops site with basic and example concepts and resources. >>> from zope.app.testing.setup import placefulSetUp, placefulTearDown >>> site = placefulSetUp(True) - >>> from loops.tests.setup import TestSite + >>> from loops.expert.testsetup import TestSite >>> t = TestSite(site) >>> concepts, resources, views = t.setup() >>> loopsRoot = site['loops'] >>> len(concepts), len(resources), len(views) - (11, 3, 0) + (30, 3, 0) Importing loops Objects @@ -44,7 +44,7 @@ Creating the corresponding objects >>> loader = Loader(loopsRoot) >>> loader.load(elements) >>> len(concepts), len(resources), len(views) - (12, 3, 0) + (31, 3, 0) >>> from loops.common import adapted >>> adMyquery = adapted(concepts['myquery']) @@ -118,7 +118,7 @@ Extracting elements >>> extractor = Extractor(loopsRoot, os.path.join(dataDirectory, 'export')) >>> elements = list(extractor.extract()) >>> len(elements) - 20 + 52 Writing object information to the external storage -------------------------------------------------- @@ -130,13 +130,13 @@ Writing object information to the external storage >>> writer = PyWriter() >>> writer.write(elements, output) >>> print output.getvalue() - type(u'customer', u'Customer', options=u'', typeInterface=u'', viewName=u'')... - type(u'query', u'Query', options=u'', typeInterface='loops.query.IQueryConcept', + type(u'country', u'Country', options=u'', typeInterface=u'', viewName=u'')... + type(u'query', u'Query', options=u'', typeInterface='loops.expert.concept.IQueryConcept', viewName=u'')... concept(u'myquery', u'My Query', u'query', options=u'option1\noption2', viewName=u'mystuff.html')... child(u'projects', u'customer', u'standard')... - resource(u'doc04.txt', u'Document 4', u'textdocument', contentType='text/restructured') + resource(u'doc04.txt', u'Document 4', u'textdocument', contentType='text/restructured')... resourceRelation(u'myquery', u'doc04.txt', u'standard') node('home', u'Home', '', u'menu', body=u'Welcome') node('myquery', u'My Query', 'home', u'page', target=u'concepts/myquery')... @@ -172,7 +172,7 @@ corresponding extractor adapter. >>> PyWriter().write(extractor.extract(), output) >>> print output.getvalue() - type(u'customer', u'Customer', options=u'', typeInterface=u'', viewName=u'')... + type(u'country', u'Country', options=u'', typeInterface=u'', viewName=u'')... concept(u'myquery', u'My Query', u'query', options=u'option1\noption2', viewName=u'mystuff.html')[ annotations(creators=(u'john',))]... diff --git a/helpers.txt b/helpers.txt index a238383..27edd4d 100755 --- a/helpers.txt +++ b/helpers.txt @@ -226,7 +226,7 @@ get a type manager from all loops objects, always with the same context: >>> types = typeManager.types >>> typeTokens = sorted(t.token for t in types) >>> len(typeTokens) - 9 + 8 >>> typeManager.getType('.loops/concepts/topic') == cc1_type True @@ -237,7 +237,7 @@ condition: >>> types = typeManager.listTypes(include=('concept',)) >>> typeTokens = sorted(t.token for t in types) >>> len(typeTokens) - 6 + 5 >>> types = typeManager.listTypes(exclude=('concept',)) >>> typeTokens = sorted(t.token for t in types) >>> len(typeTokens) @@ -313,9 +313,11 @@ We first have to set up the query type, i.e. a type concept associated with the IQueryConcept interface. The query type concept itself has already been provided by the setup, but we have to register a corresponding adapter. - >>> from loops.query import QueryConcept + >>> from loops.expert.concept import IQueryConcept, QueryConcept >>> component.provideAdapter(QueryConcept) - >>> query = concepts['query'] + >>> from loops.setup import addAndConfigureObject + >>> query = addAndConfigureObject(concepts, Concept, + ... 'query', conceptType=typeObject, typeInterface=IQueryConcept) Next we need a concept of this type: @@ -423,7 +425,6 @@ Folders structures of their documents in the filesystem to the loops concept map. - >>> from loops.setup import addAndConfigureObject >>> tFolder = addAndConfigureObject(concepts, Concept, 'folder', ... title=u'Folder', conceptType=typeObject) diff --git a/integrator/README.txt b/integrator/README.txt index 9fef34e..861ba87 100644 --- a/integrator/README.txt +++ b/integrator/README.txt @@ -27,7 +27,7 @@ configuration): >>> concepts, resources, views = t.setup() >>> len(concepts) + len(resources) - 18 + 17 External Collections diff --git a/integrator/content/README.txt b/integrator/content/README.txt index f874988..fe97840 100644 --- a/integrator/content/README.txt +++ b/integrator/content/README.txt @@ -27,7 +27,7 @@ configuration): >>> concepts, resources, views = t.setup() >>> len(concepts) + len(resources) - 14 + 13 Accessing a Directory in the Filesystem diff --git a/interfaces.py b/interfaces.py index 8fe2811..a8d4966 100644 --- a/interfaces.py +++ b/interfaces.py @@ -745,6 +745,14 @@ class IExternalFile(IFile): missing_value='', required=False) + externalAddress = schema.BytesLine( + title=_(u'External Address'), + description=_(u'The full address for accessing the object ' + 'on the external storage, e.g. a filename or path.'), + default='', + missing_value='', + required=False) + class IImage(IResourceAdapter): """ A media asset that may be embedded in a (web) page as an image. diff --git a/organize/README.txt b/organize/README.txt index a0cd7fc..4c86ee4 100644 --- a/organize/README.txt +++ b/organize/README.txt @@ -18,7 +18,7 @@ ZCML setup): >>> from loops.organize.setup import SetupManager >>> component.provideAdapter(SetupManager, name='organize') - >>> from loops.tests.setup import TestSite + >>> from loops.expert.testsetup import TestSite >>> t = TestSite(site) >>> concepts, resources, views = t.setup() @@ -237,7 +237,7 @@ Automatic security settings on persons >>> from zope.traversing.api import getName >>> list(sorted(getName(c) for c in concepts['person'].getChildren())) - [u'john', u'martha', u'person.newuser'] + [u'jim', u'john', u'martha', u'person.newuser'] Person objects that have a user assigned to them receive this user (principal) as their owner. diff --git a/organize/personal/README.txt b/organize/personal/README.txt index e4e2dfe..b50b551 100644 --- a/organize/personal/README.txt +++ b/organize/personal/README.txt @@ -72,10 +72,10 @@ So we are now ready to query the favorites. >>> favs = list(favorites.query(userName=johnCId)) >>> favs - [] + [] >>> list(favAdapted.list(johnC)) - ['29'] + ['27'] >>> util.getObjectForUid(favs[0].taskId) is resources['d001.txt'] True diff --git a/organize/stateful/README.txt b/organize/stateful/README.txt index 7d737f2..45ec8df 100644 --- a/organize/stateful/README.txt +++ b/organize/stateful/README.txt @@ -14,7 +14,7 @@ First we set up a loops site with basic and example concepts and resources. >>> from loops.organize.setup import SetupManager >>> component.provideAdapter(SetupManager, name='organize') - >>> from loops.tests.setup import TestSite + >>> from loops.expert.testsetup import TestSite >>> t = TestSite(site) >>> concepts, resources, views = t.setup() >>> loopsRoot = site['loops'] @@ -40,7 +40,11 @@ making an object statful we'll use an adapter. We may now take a document and adapt it to IStateful so that we may check the document's state and perform transitions to other states. - >>> doc01 = resources['d001.txt'] + >>> from loops.resource import Resource + >>> from loops.setup import addAndConfigureObject + >>> tText = concepts['textdocument'] + >>> doc01 = resources['doc01.txt'] = addAndConfigureObject(resources, + ... Resource, 'doc01.txt', conceptType=tText) >>> statefulDoc01 = component.getAdapter(doc01, IStateful, ... name='simple_publishing') @@ -88,7 +92,6 @@ later. >>> tCustomer = concepts['customer'] >>> from loops.concept import Concept - >>> from loops.setup import addAndConfigureObject >>> c01 = addAndConfigureObject(concepts, Concept, 'c01', conceptType=tCustomer, ... title='im publishing') >>> c02 = addAndConfigureObject(concepts, Concept, 'c02', conceptType=tCustomer, diff --git a/resource.py b/resource.py index 778679a..e678496 100644 --- a/resource.py +++ b/resource.py @@ -357,7 +357,6 @@ class ExternalFileAdapter(FileAdapter): def getExternalAddress(self): return getattr(self.context, '_externalAddress', self.context.__name__) def setExternalAddress(self, addr): - # TODO (?) - use intId as default? self.context._externalAddress = addr externalAddress = property(getExternalAddress, setExternalAddress) diff --git a/rest/README.txt b/rest/README.txt index 83b9e91..2a65ba3 100755 --- a/rest/README.txt +++ b/rest/README.txt @@ -35,7 +35,7 @@ ZCML setup): Let's look what setup has provided us with: >>> sorted(concepts) - [u'domain', u'file', u'hasType', u'note', u'predicate', u'query', + [u'domain', u'file', u'hasType', u'note', u'predicate', u'standard', u'textdocument', u'type'] diff --git a/schema.py b/schema.py index 467a262..df9e8ee 100644 --- a/schema.py +++ b/schema.py @@ -47,8 +47,10 @@ class FileSchemaFactory(SchemaFactory): def __call__(self, interface, **kw): schema = super(FileSchemaFactory, self).__call__(interface, **kw) - if 'request' in kw and kw['request'].principal.id != 'rootadmin': - schema.fields.remove('contentType') + if 'request' in kw: + principal = kw['request'].principal + if not principal or principal.id != 'rootadmin': + schema.fields.remove('contentType') return schema diff --git a/search/README.txt b/search/README.txt index cb9dad4..f350702 100755 --- a/search/README.txt +++ b/search/README.txt @@ -21,13 +21,13 @@ ZCML setup): >>> from loops.type import ConceptType, TypeConcept >>> from loops.interfaces import ITypeConcept >>> from loops.base import Loops - >>> from loops.tests.setup import TestSite + >>> from loops.expert.testsetup import TestSite >>> t = TestSite(site) >>> concepts, resources, views = t.setup() >>> loopsRoot = site['loops'] >>> query = concepts['query'] - >>> topic = concepts['topic'] = Concept(u'Topic') + >>> topic = concepts['topic'] In addition we create a concept that holds the search page and a node (page) that links to this concept: @@ -66,13 +66,13 @@ zcml in real life: >>> t = searchView.typesForSearch() >>> len(t) - 9 + 14 >>> t.getTermByToken('loops:resource:*').title 'Any Resource' >>> t = searchView.conceptTypesForSearch() >>> len(t) - 4 + 9 >>> t.getTermByToken('loops:concept:*').title 'Any Concept' @@ -91,7 +91,7 @@ a controller attribute for the search view. >>> searchView.submitReplacing('1.results', '1.search.form', pageView) 'submitReplacing("1.results", "1.search.form", - "http://127.0.0.1/loops/views/page/.target29/@@searchresults.html");...' + "http://127.0.0.1/loops/views/page/.target80/@@searchresults.html");...' Basic (text/title) search ------------------------- @@ -177,7 +177,7 @@ of the concepts' titles: >>> request = TestRequest(form=form) >>> view = Search(page, request) >>> view.listConcepts() - u"{identifier: 'id', items: [{label: 'Zope (Topic)', name: 'Zope', id: '34'}, {label: 'Zope 2 (Topic)', name: 'Zope 2', id: '37'}, {label: 'Zope 3 (Topic)', name: 'Zope 3', id: '39'}]}" + u"{identifier: 'id', items: [{label: 'Zope (Topic)', name: 'Zope', id: '85'}, {label: 'Zope 2 (Topic)', name: 'Zope 2', id: '87'}, {label: 'Zope 3 (Topic)', name: 'Zope 3', id: '89'}]}" Preset Concept Types on Search Forms ------------------------------------ @@ -193,8 +193,8 @@ Let's start with a new type, the customer type. >>> custType.options [] - >>> cust1 = concepts['cust1'] = Concept(u'Zope Corporation') - >>> cust2 = concepts['cust2'] = Concept(u'cyberconcepts') + >>> cust1 = concepts['cust1'] + >>> cust2 = concepts['cust2'] >>> for c in (cust1, cust2): ... c.conceptType = customer ... catalog.index_doc(int(util.getUidForObject(c)), c) @@ -219,16 +219,17 @@ and thus include the customer type in the preset search types. >>> searchView.conceptsForType('loops:concept:customer') [{'token': 'none', 'title': u'not selected'}, - {'token': '47', 'title': u'Zope Corporation'}, - {'token': '49', 'title': u'cyberconcepts'}] + {'token': '58', 'title': u'Customer 1'}, + {'token': '60', 'title': u'Customer 2'}, + {'token': '62', 'title': u'Customer 3'}] Let's use this new search option for querying: - >>> form = {'search.4.text_selected': u'47'} + >>> form = {'search.4.text_selected': u'58'} >>> resultsView = SearchResults(page, TestRequest(form=form)) >>> results = list(resultsView.results) >>> results[0].title - u'Zope Corporation' + u'Customer 1' Automatic Filtering diff --git a/search/browser.py b/search/browser.py index 1f523fb..3579625 100644 --- a/search/browser.py +++ b/search/browser.py @@ -36,7 +36,8 @@ from cybertools.typology.interfaces import ITypeManager from loops.browser.common import BaseView from loops.browser.node import NodeView from loops.common import adapted -from loops.query import ConceptQuery, FullQuery +#from loops.query import ConceptQuery, FullQuery +from loops.expert.concept import ConceptQuery, FullQuery from loops import util from loops.util import _ diff --git a/setup.py b/setup.py index dfccbda..fd043e9 100644 --- a/setup.py +++ b/setup.py @@ -35,7 +35,7 @@ from loops.common import adapted from loops.concept import ConceptManager, Concept from loops.interfaces import ILoops, ITypeConcept from loops.interfaces import IFile, IImage, ITextDocument, INote -from loops.query import IQueryConcept +#from loops.query import IQueryConcept from loops.record import RecordManager from loops.resource import ResourceManager, Resource from loops.view import ViewManager, Node @@ -82,16 +82,16 @@ class SetupManager(object): predicate = self.addObject(conceptManager, Concept, 'predicate', title=u'Predicate') standard = self.addObject(conceptManager, Concept, 'standard', title=u'subobject') domain = self.addObject(conceptManager, Concept, 'domain', title=u'Domain') - query = self.addObject(conceptManager, Concept, 'query', title=u'Query') + #query = self.addObject(conceptManager, Concept, 'query', title=u'Query') file = self.addObject(conceptManager, Concept, 'file', title=u'File') textdocument = self.addObject(conceptManager, Concept, 'textdocument', title=u'Text') note = self.addObject(conceptManager, Concept, 'note', title=u'Note') - for c in (typeConcept, domain, query, note, file, textdocument, predicate): + for c in (typeConcept, domain, note, file, textdocument, predicate): c.conceptType = typeConcept notify(ObjectModifiedEvent(c)) ITypeConcept(typeConcept).typeInterface = ITypeConcept - ITypeConcept(query).typeInterface = IQueryConcept + #ITypeConcept(query).typeInterface = IQueryConcept ITypeConcept(file).typeInterface = IFile ITypeConcept(textdocument).typeInterface = ITextDocument ITypeConcept(note).typeInterface = INote diff --git a/system/README.txt b/system/README.txt index dd3acd7..3bc1aae 100644 --- a/system/README.txt +++ b/system/README.txt @@ -39,7 +39,7 @@ In addition to the application site we need a loops system management site. >>> systemRoot = site['loops.system'] >>> sorted(sysConcepts) - [u'domain', u'file', u'hasType', u'job', u'note', u'predicate', u'query', + [u'domain', u'file', u'hasType', u'job', u'note', u'predicate', u'standard', u'textdocument', u'type'] diff --git a/tests/setup.py b/tests/setup.py index d119d48..d732629 100644 --- a/tests/setup.py +++ b/tests/setup.py @@ -33,6 +33,7 @@ from cybertools.composer.schema.factory import SchemaFactory from cybertools.composer.schema.field import FieldInstance, NumberFieldInstance from cybertools.composer.schema.field import DateFieldInstance, BooleanFieldInstance from cybertools.composer.schema.field import EmailFieldInstance, ListFieldInstance +from cybertools.composer.schema.field import FileUploadFieldInstance from cybertools.composer.schema.instance import Instance, Editor from cybertools.relation.tests import IntIdsStub from cybertools.relation.registry import RelationRegistry, IIndexableRelation @@ -56,8 +57,7 @@ from loops.layout.base import LayoutNode from loops.organize.memberinfo import MemberInfoProvider from loops.organize.stateful.base import StatefulResourceIndexInfo, handleTransition from loops.predicate import Predicate #, MappingAttributeRelation -from loops.query import QueryConcept -from loops.query import QueryConcept +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 @@ -139,6 +139,7 @@ class TestSite(object): component.provideAdapter(EmailFieldInstance, name='email') component.provideAdapter(BooleanFieldInstance, name='boolean') component.provideAdapter(ListFieldInstance, name='list') + component.provideAdapter(FileUploadFieldInstance, name='fileupload') component.provideAdapter(SchemaFactory) component.provideAdapter(ResourceSchemaFactory) component.provideAdapter(FileSchemaFactory) diff --git a/versioning/README.txt b/versioning/README.txt index f934e26..2fd684d 100644 --- a/versioning/README.txt +++ b/versioning/README.txt @@ -29,7 +29,7 @@ configuration): >>> #sorted(concepts) >>> #sorted(resources) >>> len(concepts) + len(resources) - 24 + 23 Version Information diff --git a/xmlrpc/README.txt b/xmlrpc/README.txt index 5f7b15d..e513779 100755 --- a/xmlrpc/README.txt +++ b/xmlrpc/README.txt @@ -34,7 +34,7 @@ ZCML setup): Let's look what setup has provided us with: >>> len(concepts) - 19 + 18 Now let's add a few more concepts: @@ -58,7 +58,7 @@ There are a few standard objects we can retrieve directly: >>> defaultPred = xrf.getDefaultPredicate() >>> defaultPred['id'], defaultPred['name'] - ('16', u'standard') + ('14', u'standard') >>> typePred = xrf.getTypePredicate() >>> typePred['id'], typePred['name'] ('1', u'hasType') @@ -71,7 +71,7 @@ note that the 'hasType' predicate is not shown as it should not be applied in an explicit assignment. >>> sorted(t['name'] for t in xrf.getConceptTypes()) - [u'customer', u'domain', u'file', u'note', u'person', u'predicate', u'query', + [u'customer', u'domain', u'file', u'note', u'person', u'predicate', u'task', u'textdocument', u'topic', u'type'] >>> sorted(t['name'] for t in xrf.getPredicates()) [u'depends', u'knows', u'ownedby', u'provides', u'requires', u'standard'] @@ -80,10 +80,10 @@ We can also retrieve a certain object by its id or its name: >>> obj2 = xrf.getObjectById('5') >>> obj2['id'], obj2['name'] - ('5', u'query') + ('5', u'note') >>> textdoc = xrf.getObjectByName(u'textdocument') >>> textdoc['id'], textdoc['name'] - ('11', u'textdocument') + ('9', u'textdocument') All methods that retrieve one object also returns its children and parents: @@ -94,7 +94,7 @@ All methods that retrieve one object also returns its children and parents: u'hasType' >>> sorted(c['name'] for c in ch[0]['objects']) [u'customer', u'domain', u'file', u'note', u'person', u'predicate', - u'query', u'task', u'textdocument', u'topic', u'type'] + u'task', u'textdocument', u'topic', u'type'] >>> pa = defaultPred['parents'] >>> len(pa) @@ -113,7 +113,7 @@ We can also retrieve children and parents explicitely: u'hasType' >>> sorted(c['name'] for c in ch[0]['objects']) [u'customer', u'domain', u'file', u'note', u'person', u'predicate', - u'query', u'task', u'textdocument', u'topic', u'type'] + u'task', u'textdocument', u'topic', u'type'] >>> pa = xrf.getParents('7') >>> len(pa) @@ -172,14 +172,14 @@ Updating the concept map >>> topicId = xrf.getObjectByName('topic')['id'] >>> xrf.createConcept(topicId, u'zope2', u'Zope 2') - {'description': u'', 'title': u'Zope 2', 'type': '24', 'id': '56', + {'description': u'', 'title': u'Zope 2', 'type': '22', 'id': '54', 'name': u'zope2'} The name of the concept is checked by a name chooser; if the corresponding parameter is empty, the name will be generated from the title. >>> xrf.createConcept(topicId, u'', u'Python') - {'description': u'', 'title': u'Python', 'type': '24', 'id': '58', + {'description': u'', 'title': u'Python', 'type': '22', 'id': '56', 'name': u'python'} If we try to deassign a ``hasType`` relation nothing will happen; a