diff --git a/README.txt b/README.txt index 11c4551..b162a1a 100755 --- a/README.txt +++ b/README.txt @@ -49,7 +49,7 @@ default predicate concept; the default name for this is 'standard'. >>> from cybertools.relation.registry import DummyRelationRegistry >>> from zope.app.testing import ztapi >>> ztapi.provideUtility(IRelationRegistry, DummyRelationRegistry()) - >>> concepts['standard'] = Concept('parent') + >>> concepts['standard'] = Concept(u'subconcept') Now we can assign the concept c2 as a child to c1 (using the standard ConceptRelation): @@ -103,7 +103,21 @@ We get a list of types using the ConceptTypeSourceList: >>> types = ConceptTypeSourceList(cc1) >>> sorted(t.title for t in types) [u'Topic', u'Type', u'Unknown Type'] - + +Using a PredicateSourceList we can retrieve a list of the available +predicates. In order for this to work we first have to assign our predicates +a special concept type. + + >>> concepts['predicate'] = Concept(u'Predicate') + >>> predicate = concepts['predicate'] + >>> concepts['hasType'].conceptType = predicate + >>> concepts['standard'].conceptType = predicate + + >>> from loops.concept import PredicateSourceList + >>> predicates = PredicateSourceList(cc1) + >>> sorted(t.title for t in predicates) + [u'has type', u'subconcept'] + Concept Views ------------- @@ -151,6 +165,24 @@ We can also create a new concept and assign it: >>> 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: + + >>> from zope.publisher.interfaces.browser import IBrowserRequest + >>> from loops.browser.common import LoopsTerms + >>> from zope.app.form.browser.interfaces import ITerms + >>> from zope.schema.interfaces import IIterableSource + >>> ztapi.provideAdapter(IIterableSource, ITerms, LoopsTerms, + ... with=(IBrowserRequest,)) + + >>> sorted((t.title, t.token) for t in view.conceptTypes()) + [(u'Topic', '.loops/concepts/topic'), (u'Type', '.loops/concepts/type'), + (u'Unknown Type', '.loops/concepts/unknown')] + + >>> sorted((t.title, t.token) for t in view.predicates()) + [(u'has type', '.loops/concepts/hasType'), + (u'subconcept', '.loops/concepts/standard')] + Searchable Text Adapter ----------------------- @@ -406,7 +438,6 @@ objects.) The source is basically a source list: The form then uses a sort of browser view providing the ITerms interface based on this source list: - >>> from loops.browser.common import LoopsTerms >>> terms = LoopsTerms(source, TestRequest()) >>> term = terms.getTerm(doc1) >>> term.token, term.title, term.value @@ -471,7 +502,6 @@ A node's target is rendered using the NodeView's renderTargetBody() method. This makes use of a browser view registered for the target interface, and of a lot of other stuff needed for the rendering machine. - >>> from zope.publisher.interfaces.browser import IBrowserRequest >>> from zope.app.publisher.interfaces.browser import IBrowserView >>> from loops.browser.resource import DocumentView >>> ztapi.provideAdapter(IDocument, Interface, DocumentView, diff --git a/browser/concept.py b/browser/concept.py index 0380335..6916d98 100644 --- a/browser/concept.py +++ b/browser/concept.py @@ -31,9 +31,11 @@ from zope.cachedescriptors.property import Lazy from zope.event import notify from zope.interface import implements from zope.publisher.interfaces import BadRequest +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.concept import Concept +from loops.concept import Concept, ConceptTypeSourceList, PredicateSourceList from loops.browser.common import BaseView, LoopsTerms @@ -46,30 +48,16 @@ class ConceptView(BaseView): def parents(self): for r in self.context.getParentRelations(): yield ConceptRelationView(r, self.request) - #rels = self.context.getParentRelations() - #result = [] - #for r in rels: - # p = r.first - #if p is None: # this should not be necessary - # print 'Warning: parents() got a None first on', \ - # zapi.getName(self.context), zapi.getName(r.predicate) - # continue - # p.predicate = r.predicate - #return result and self.viewIterator(result) or [] - - def viewIterator(self, objs): - request = self.request - for o in objs: - yield ConceptView(o, request) def update(self): - action = self.request.get('action') + request = self.request + action = request.get('action') if action is None: return True if action == 'create': self.createAndAssign() return True - tokens = self.request.get('tokens', []) + tokens = request.get('tokens', []) for token in tokens: parts = token.split(':') token = parts[0] @@ -77,16 +65,20 @@ class ConceptView(BaseView): relToken = parts[1] concept = self.loopsRoot.loopsTraverse(token) if action == 'assign': - assignAs = self.request.get('assignAs', 'child') + assignAs = request.get('assignAs', 'child') + predicate = request.get('predicate') or None + if predicate: + predicate = removeSecurityProxy( + self.loopsRoot.loopsTraverse(predicate)) if assignAs == 'child': - self.context.assignChild(removeSecurityProxy(concept)) + self.context.assignChild(removeSecurityProxy(concept), predicate) elif assignAs == 'parent': - self.context.assignParent(removeSecurityProxy(concept)) + self.context.assignParent(removeSecurityProxy(concept), predicate) else: raise(BadRequest, 'Illegal assignAs parameter: %s.' % assignAs) elif action == 'remove': predicate = self.loopsRoot.loopsTraverse(relToken) - qualifier = self.request.get('qualifier') + qualifier = request.get('qualifier') if qualifier == 'parents': self.context.deassignParent(concept, [predicate]) elif qualifier == 'children': @@ -103,17 +95,23 @@ class ConceptView(BaseView): if not name: raise(BadRequest, 'Empty name.') title = request.get('create.title', u'') - type = request.get('create.type') + conceptType = request.get('create.type') concept = Concept(title) container = self.loopsRoot.getConceptManager() container[name] = concept - # TODO: notify ObjectCreatedEvent() (?) - #notify(ObjectCreatedEvent(removeSecurityProxy(concept))) + if conceptType: + ctype = self.loopsRoot.loopsTraverse(conceptType) + concept.conceptType = ctype + notify(ObjectCreatedEvent(removeSecurityProxy(concept))) assignAs = self.request.get('assignAs', 'child') + predicate = request.get('create.predicate') or None + if predicate: + predicate = removeSecurityProxy( + self.loopsRoot.loopsTraverse(predicate)) if assignAs == 'child': - self.context.assignChild(removeSecurityProxy(concept)) + self.context.assignChild(removeSecurityProxy(concept), predicate) elif assignAs == 'parent': - self.context.assignParent(removeSecurityProxy(concept)) + self.context.assignParent(removeSecurityProxy(concept), predicate) else: raise(BadRequest, 'Illegal assignAs parameter: %s.' % assignAs) @@ -127,8 +125,32 @@ class ConceptView(BaseView): result = cat.searchResults(loops_searchableText=searchTerm) else: result = self.loopsRoot.getConceptManager().values() + searchType = request.get('searchType', '*') + # TODO: query catalog for type + if not searchType: + result = [r for r in result if r.conceptType is None] + elif searchType != '*': + type = self.loopsRoot.loopsTraverse(searchType) + result = [r for r in result if r.conceptType == type] return self.viewIterator(result) + def viewIterator(self, objs): + request = self.request + for o in objs: + yield ConceptView(o, request) + + def conceptTypes(self): + types = ConceptTypeSourceList(self.context) + terms = zapi.getMultiAdapter((types, self.request), ITerms) + for type in types: + yield terms.getTerm(type) + + def predicates(self): + preds = PredicateSourceList(self.context) + terms = zapi.getMultiAdapter((preds, self.request), ITerms) + for pred in preds: + yield terms.getTerm(pred) + class ConceptRelationView(object): diff --git a/browser/concept_related.pt b/browser/concept_related.pt index 74840b3..92d0f40 100644 --- a/browser/concept_related.pt +++ b/browser/concept_related.pt @@ -12,6 +12,7 @@ qualifier string:parents; summary string:Currently assigned objects; legend string:Parent Concepts; + showPredicate string:yes; buttonText string:Remove Parents;" style="float:left; padding-right:20px"> @@ -21,6 +22,7 @@ qualifier string:children; summary string:Currently assigned objects; legend string:Child Concepts; + showPredicate string:yes; buttonText string:Remove Children;" style="padding-right:20px"> @@ -37,6 +39,7 @@ qualifier nothing; summary string:Assignment candidates; legend string:Search; + showPredicate nothing; buttonText string:Assign;" style="padding-right:20px"> @@ -49,7 +52,15 @@ - object(s) + Object(s) using Predicate + diff --git a/browser/configure.zcml b/browser/configure.zcml index 0ac0c91..387b3c1 100644 --- a/browser/configure.zcml +++ b/browser/configure.zcml @@ -418,6 +418,10 @@ for="loops.concept.ConceptTypeSourceList zope.publisher.interfaces.browser.IBrowserRequest" /> + + diff --git a/browser/relation_macros.pt b/browser/relation_macros.pt index 5924ffb..a120a68 100644 --- a/browser/relation_macros.pt +++ b/browser/relation_macros.pt @@ -22,8 +22,9 @@   Title - Predicate Type + Predicate @@ -39,21 +40,19 @@ Title - - - - Type - - - - Title + Type + + + + + Predicate @@ -87,8 +86,13 @@ tal:attributes="value nothing" />  Type
@@ -96,11 +100,20 @@ value="Create Object" i18n:attributes="value" tal:attributes="value buttonText" /> - and assign as - + and assign as + + using Predicate +
@@ -111,15 +124,23 @@
-
+
Search Term + tal:attributes="value searchTerm" /> Type - - + + + +
diff --git a/concept.py b/concept.py index 0f5ae10..135f195 100644 --- a/concept.py +++ b/concept.py @@ -252,10 +252,9 @@ class ConceptTypeSourceList(object): result = [] cm = self.concepts typeObject = cm.getTypeConcept() - unknownType = self.concepts.get('unknown') + unknownType = cm.get('unknown') # does this make sense? if typeObject is not None: - typeRelation = ConceptRelation(None, typeObject, cm.getTypePredicate()) - types = typeObject.getChildren((typeRelation,)) + types = typeObject.getChildren([cm.getTypePredicate()]) if typeObject not in types: result.append(typeObject) if unknownType is not None and unknownType not in types: @@ -267,6 +266,36 @@ class ConceptTypeSourceList(object): return len(self.conceptTypes) +class PredicateSourceList(object): + + implements(schema.interfaces.IIterableSource) + + def __init__(self, context): + self.context = context + self.concepts = self.context.getLoopsRoot().getConceptManager() + + def __iter__(self): + return iter(self.predicates) + + @Lazy + def predicates(self): + result = [] + cm = self.concepts + defPred = cm.getDefaultPredicate() + typePred = cm.getTypePredicate() + if defPred is not None and typePred is not None: + result.append(defPred) + result.append(typePred) + predType = defPred.conceptType + if predType is not None and predType != cm.getTypeConcept(): + result.extend(p for p in predType.getChildren([typePred]) + if p not in result) + return result + + def __len__(self): + return len(self.conceptTypes) + + class SearchableText(object): implements(ISearchableText) diff --git a/configure.zcml b/configure.zcml index 0536d19..a7af217 100644 --- a/configure.zcml +++ b/configure.zcml @@ -252,6 +252,11 @@ name="loops.conceptTypeSource" /> + +