diff --git a/browser/common.py b/browser/common.py index d1d4062..9b7c3c2 100644 --- a/browser/common.py +++ b/browser/common.py @@ -96,6 +96,10 @@ class BaseView(object): def url(self): return zapi.absoluteURL(self.context, self.request) + @Lazy + def view(self): + return self + @Lazy def token(self): return self.loopsRoot.getLoopsUri(self.context) diff --git a/browser/concept.py b/browser/concept.py index 63cb7cc..ba22b07 100644 --- a/browser/concept.py +++ b/browser/concept.py @@ -22,6 +22,7 @@ Definition of the Task view class. $Id$ """ +from zope import interface, component, schema from zope.app import zapi from zope.app.catalog.interfaces import ICatalog from zope.app.event.objectevent import ObjectCreatedEvent, ObjectModifiedEvent @@ -35,7 +36,6 @@ from zope.formlib.form import EditForm, FormFields 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 @@ -78,6 +78,25 @@ class ConceptView(BaseView): for r in self.context.getResourceRelations(): yield ConceptRelationView(r, self.request, contextIsSecond=True) + @Lazy + def view(self): + ti = IType(self.context).typeInterface + # TODO: check the interface (maybe for a base interface IViewProvider) + # instead of the viewName attribute: + if ti and 'viewName' in ti: + typeAdapter = ti(self.context) + viewName = typeAdapter.viewName + # ??? Would it make sense to use a somehow restricted interface + # that should be provided by the view like IQuery? + #viewInterface = getattr(typeAdapter, 'viewInterface', None) or IQuery + if viewName: + adapter = component.queryMultiAdapter((self.context, self.request), + interface.Interface, name=viewName) + if adapter is not None: + return adapter + #elif type provides view: use this + return self + class ConceptConfigureView(ConceptView): diff --git a/browser/node.py b/browser/node.py index c2745b5..ea1b0c6 100644 --- a/browser/node.py +++ b/browser/node.py @@ -105,7 +105,9 @@ class NodeView(BaseView): def target(self): obj = self.targetObject if obj is not None: - return zapi.getMultiAdapter((obj, self.request)) + basicView = zapi.getMultiAdapter((obj, self.request)) + return basicView.view + #return zapi.getMultiAdapter((obj, self.request)) def renderTarget(self): target = self.target @@ -117,6 +119,7 @@ class NodeView(BaseView): @Lazy def bodyMacro(self): + # ?TODO: replace by: return self.target.macroName target = self.targetObject if target is None or IDocument.providedBy(target): return 'textbody' diff --git a/browser/node_macros.pt b/browser/node_macros.pt index 5eebb5e..5cd8dd7 100644 --- a/browser/node_macros.pt +++ b/browser/node_macros.pt @@ -39,8 +39,10 @@ ondblclick python: item.openEditWindow('configure.html')"> Node Body - -
+ +
+ diff --git a/configure.zcml b/configure.zcml index f46b0b9..962fd7c 100644 --- a/configure.zcml +++ b/configure.zcml @@ -279,6 +279,7 @@ + diff --git a/helpers.txt b/helpers.txt index 7b9cce4..5697b5d 100755 --- a/helpers.txt +++ b/helpers.txt @@ -215,7 +215,6 @@ Type-based interfaces and adapters A type has an optional typeInterface attribute that objects of this type will be adaptable to. The default for this is None: - >>> cc1_type.typeInterface >>> cc1_type.typeInterface is None True @@ -235,7 +234,7 @@ i.e. the 'topic' concept, via an adapter: >>> class Topic(object): ... implements(ITopic) ... def __init__(self, context): pass - >>> ztapi.provideAdapter(IConcept, ITopic, Topic) + >>> component.provideAdapter(Topic, (IConcept,), ITopic) >>> ITypeConcept(topic).typeInterface = ITopic >>> cc1.conceptType = topic @@ -246,6 +245,57 @@ i.e. the 'topic' concept, via an adapter: True +Concepts as queries +------------------- + +We first have to set up the query type, i.e. a type concept associated +with the IQueryConcept interface: + + >>> from loops.query import IQueryConcept, QueryConcept + >>> component.provideAdapter(QueryConcept, (IConcept,), IQueryConcept) + + >>> query = concepts['query'] = Concept(u'Query') + >>> query.conceptType = typeObject + >>> ITypeConcept(query).typeInterface = IQueryConcept + +Next we need a concept of this type: + + >>> simpleQuery = concepts['simpleQuery'] = Concept(u'Simple query') + >>> simpleQuery.conceptType = query + >>> sq_type = IType(simpleQuery) + >>> sq_adapter = sq_type.typeInterface(simpleQuery) + >>> sq_adapter.viewName = 'simpleview.html' + >>> simpleQuery._viewName + 'simpleview.html' + +This viewName attribute of the query will be automatically used by +a concept view when asked for the view that should be used for rendering +the concept... + + >>> from loops.browser.concept import ConceptView + >>> from zope.publisher.browser import TestRequest + >>> sq_baseView = ConceptView(simpleQuery, TestRequest()) + >>> sq_view = sq_baseView.view + +...but only when the view exists, i.e. there is a class registered as a +view/multi-adapter with this name: + + >>> sq_view is sq_baseView + True + + >>> class SimpleView(object): + ... def __init__(self, context, request): pass + >>> from zope.publisher.interfaces.browser import IBrowserRequest + >>> component.provideAdapter(SimpleView, (IConcept, IBrowserRequest), Interface, + ... name='simpleview.html') + >>> sq_baseView = ConceptView(simpleQuery, TestRequest()) + >>> sq_view = sq_baseView.view + >>> sq_view is sq_baseView + False + >>> sq_view.__class__ + + + Controlling presentation using view properties ---------------------------------------------- diff --git a/organize/README.txt b/organize/README.txt index 8df35df..42564b2 100644 --- a/organize/README.txt +++ b/organize/README.txt @@ -22,8 +22,7 @@ ZCML setup): >>> from loops.concept import ConceptManager, Concept >>> from loops.interfaces import IConcept, ITypeConcept - >>> site['loops'] = Loops() - >>> loopsRoot = site['loops'] + >>> loopsRoot = site['loops'] = Loops() >>> from cybertools.relation.interfaces import IRelationRegistry >>> from cybertools.relation.registry import DummyRelationRegistry diff --git a/organize/interfaces.py b/organize/interfaces.py index ed0ce07..7ec0324 100644 --- a/organize/interfaces.py +++ b/organize/interfaces.py @@ -54,13 +54,14 @@ class UserId(schema.TextLine): try: principal = auth.getPrincipal(userId) except PrincipalLookupError: - raiseValidationError(u'User %s does not exist' % userId) + raiseValidationError(_(u'User $userId does not exist', + mapping={'userId': userId})) pa = annotations(principal) person = pa.get(ANNOTATION_KEY, None) if person is not None and person != self.context: raiseValidationError( - u'There is alread a person (%s) assigned to user %s.' - % (zapi.getName(person), userId)) + _(u'There is alread a person ($person) assigned to user $userId.', + mapping={'person': zapi.getName(person), 'userId': userId})) class IPerson(IBasePerson): diff --git a/organize/party.py b/organize/party.py index bfb4ae7..3a1f793 100644 --- a/organize/party.py +++ b/organize/party.py @@ -37,7 +37,7 @@ from cybertools.organize.party import Person as BasePerson from cybertools.typology.interfaces import IType from loops.interfaces import IConcept from loops.organize.interfaces import IPerson, ANNOTATION_KEY -from loops.type import TypeInterfaceSourceList +from loops.type import TypeInterfaceSourceList, AdapterBase # register IPerson as a type interface - (TODO: use a function for this) @@ -45,34 +45,14 @@ from loops.type import TypeInterfaceSourceList TypeInterfaceSourceList.typeInterfaces += (IPerson,) -class Person(BasePerson): +class Person(AdapterBase, BasePerson): """ typeInterface adapter for concepts of type 'person'. """ implements(IPerson) - adapts(IConcept) - __attributes = ('context', '__parent__', 'userId',) - __schemas = list(IPerson) + list(IConcept) - - def __init__(self, context): - self.context = context # to get the permission stuff right - self.__parent__ = context - - def __getattr__(self, attr): - self.checkAttr(attr) - return getattr(self.context, '_' + attr, None) - - def __setattr__(self, attr, value): - if attr in self.__attributes: - object.__setattr__(self, attr, value) - else: - self.checkAttr(attr) - setattr(self.context, '_' + attr, value) - - def checkAttr(self, attr): - if attr not in self.__schemas: - raise AttributeError(attr) + _attributes = ('context', '__parent__', 'userId',) + _schemas = list(IPerson) + list(IConcept) def getUserId(self): return getattr(self.context, '_userId', None) diff --git a/query.py b/query.py new file mode 100644 index 0000000..1efd03e --- /dev/null +++ b/query.py @@ -0,0 +1,70 @@ +# +# Copyright (c) 2006 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 management stuff. + +$Id$ +""" + +from zope.app import zapi +from zope.component import adapts +from zope.interface import Interface, Attribute, implements +from zope.i18nmessageid import MessageFactory +from zope.cachedescriptors.property import Lazy +from zope import schema +from zope.security.proxy import removeSecurityProxy + +from cybertools.typology.type import BaseType, TypeManager +from loops.interfaces import IConcept +from loops.type import AdapterBase, TypeInterfaceSourceList + +_ = MessageFactory('loops') + + +class IQuery(Interface): + """ The basic query interface. + """ + + def query(self, **kw): + """ Execute the query and return a sequence of objects. + """ + + +class IQueryConcept(Interface): + """ The schema for the query type. + """ + + viewName = schema.TextLine( + title=_(u'Adapter/View name'), + description=_(u'The name of the (mulit-) adapter (typically a view) ' + 'to be used for the query and for presenting ' + 'the results'), + default=u'', + required=True) + + +class QueryConcept(AdapterBase): + + implements(IQueryConcept) + + _schemas = list(IQueryConcept) + list(IConcept) + + +TypeInterfaceSourceList.typeInterfaces += (IQueryConcept,) + diff --git a/type.py b/type.py index 84e20ba..f5f73ad 100644 --- a/type.py +++ b/type.py @@ -215,3 +215,33 @@ class TypeInterfaceSourceList(object): def __len__(self): return len(self.typeInterfaces) + +class AdapterBase(object): + """ (Mix-in) Class for concept adapters that provide editing of fields + defined by the type interface. + """ + + adapts(IConcept) + + _attributes = ('context', '__parent__', ) + _schemas = (IConcept,) + + def __init__(self, context): + self.context = context # to get the permission stuff right + self.__parent__ = context + + def __getattr__(self, attr): + self.checkAttr(attr) + return getattr(self.context, '_' + attr, None) + + def __setattr__(self, attr, value): + if attr in self._attributes: + object.__setattr__(self, attr, value) + else: + self.checkAttr(attr) + setattr(self.context, '_' + attr, value) + + def checkAttr(self, attr): + if attr not in self._schemas: + raise AttributeError(attr) +