diff --git a/wiki/base/config.py b/wiki/base/config.py index dc3b8fd..e2d87a8 100644 --- a/wiki/base/config.py +++ b/wiki/base/config.py @@ -38,17 +38,36 @@ class WikiConfigInfo(dict): return self.get(attr, None) +class BaseConfigurator(object): + + def __init__(self, context): + self.context = context + + def initialize(self): + ci = WikiConfigInfo() + self.context._configInfo = ci + return ci + + def getConfigInfo(self): + return self.context._configInfo + + class BaseConfiguration(object): """ The base class for all wiki configuration implementations. """ implements(IWikiConfiguration) + configurator = BaseConfigurator + _configInfo = None + def getConfigInfo(self): + return self.configurator(self).getConfigInfo() + def getConfig(self, functionality): c = None - ci = self._configInfo + ci = self.getConfigInfo() if ci is not None: c = ci.get(functionality) if c is None: @@ -58,9 +77,10 @@ class BaseConfiguration(object): return c def setConfig(self, functionality, value): - if self._configInfo is None: - self._configInfo = WikiConfigInfo() - self._configInfo.set(functionality, value) + ci = self.getConfigInfo() + if ci is None: + ci = self.configurator(self).initialize() + ci.set(functionality, value) def getConfigParent(self): return None diff --git a/wiki/base/wiki.py b/wiki/base/wiki.py index 181dfe0..469e1fa 100644 --- a/wiki/base/wiki.py +++ b/wiki/base/wiki.py @@ -38,15 +38,16 @@ class WikiManager(BaseConfiguration): implements(IWikiManager) - def __init__(self): + def setup(self): self.wikis = {} + self.plugins = {} def addWiki(self, wiki): name = wiki.name if name in self.wikis: raise ValueError("Wiki '%s' already registered." % name) self.wikis[name] = wiki - wiki.manager = self + wiki.setManager(self) return wiki def removeWiki(self, wiki): @@ -57,16 +58,25 @@ class WikiManager(BaseConfiguration): def listWikis(self): return self.wikis.values() - def getPlugin(self, type, name): + def getPlugin(self, type, name=None): + plugins = self.getPlugins() + if (type, name) in plugins: + plugin = plugins[(type, name)] + if type is None: + return plugin + return type(plugin) return component.getUtility(type, name=name) + def getPlugins(self): + return self.plugins + def getUid(self, obj): - return component.getUtility(IIntIds).getId(obj) + return self.getPlugin(IIntIds).register(obj) def getObject(self, uid): obj = self.resolveUid(uid) if obj is None: - return component.getUtility(IIntIds).getObject(int(uid)) + return self.getPlugin(IIntIds).getObject(int(uid)) return obj def resolveUid(self, uid): @@ -92,10 +102,19 @@ class Wiki(BaseConfiguration): self.name = name self.title = title or name self.pages = {} + self.setup() + + def setup(self): + self.getManager().addWiki(self) + self.createPage('StartPage', u'Start Page', + u'The text of the **Start Page**') def getManager(self): return self.manager + def setManager(self, manager): + self.manager = manager + def createPage(self, name, title=None): if name in self.pages: raise ValueError("Name '%s' already present." % name) @@ -138,6 +157,10 @@ class WikiPage(BaseConfiguration): def __init__(self, name, title=None): self.name = name self.title = title or name + self.setup() + + def setup(self): + pass def getWiki(self): return self.wiki diff --git a/wiki/browser/__init__.py b/wiki/browser/__init__.py new file mode 100644 index 0000000..38314f3 --- /dev/null +++ b/wiki/browser/__init__.py @@ -0,0 +1,3 @@ +""" +$Id$ +""" diff --git a/wiki/generic/__init__.py b/wiki/generic/__init__.py new file mode 100644 index 0000000..38314f3 --- /dev/null +++ b/wiki/generic/__init__.py @@ -0,0 +1,3 @@ +""" +$Id$ +""" diff --git a/wiki/generic/wiki.py b/wiki/generic/wiki.py new file mode 100644 index 0000000..c8d77be --- /dev/null +++ b/wiki/generic/wiki.py @@ -0,0 +1,179 @@ +# +# 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 +# + +""" +Wiki implementation = adapters for Zope2 content objects. + +$Id$ +""" + +from Acquisition import aq_inner, aq_parent +from BTrees.IOBTree import IOTreeSet +from BTrees.OOBTree import OOBTree +from persistent.mapping import PersistentMapping +from zope.app.intid import IntIds +from zope.app.intid.interfaces import IIntIds +from zope.cachedescriptors.property import Lazy +from zope import component +from zope.component import adapts +from zope.interface import implements + +from cybertools.util.generic.interfaces import IGenericObject, IGenericFolder +from cybertools.wiki.base.config import WikiConfigInfo, BaseConfigurator +from cybertools.wiki.base.wiki import WikiManager as BaseWikiManager +from cybertools.wiki.base.wiki import Wiki as BaseWiki +from cybertools.wiki.base.wiki import WikiPage as BaseWikiPage +from cybertools.wiki.interfaces import ILinkManager, IWikiConfigInfo +from cybertools.wiki.tracking.link import Link, TrackingStorage + + +class PersistentConfigInfo(PersistentMapping): + + implements(IWikiConfigInfo) + + def set(self, functionality, value): + self[functionality] = value + + def __getattr__(self, attr): + return self.get(attr, None) + + +class GenericConfigurator(BaseConfigurator): + + def initialize(self): + ci = PersistentConfigInfo() + self.context.context.setGenericAttribute('configInfo', ci) + return ci + + def getConfigInfo(self): + return self.context.context.getGenericAttribute('configInfo', None) + + +class WikiManager(BaseWikiManager): + + adapts(IGenericObject) + + configurator = GenericConfigurator + + def __init__(self, context): + self.context = context + + def setup(self): + self.context.setGenericAttribute('wikis', IOTreeSet()) + plugins = self.context.setGenericAttribute('plugins', PersistentMapping()) + plugins[(IIntIds, None)] = IntIds() + linkStorage = TrackingStorage(trackFactory=Link) + plugins[(ILinkManager, 'tracking')] = linkStorage + self.setConfig('linkManager', 'tracking') + + def addWiki(self, wiki): + uid = self.getUid(wiki) + self.wikiUids.insert(uid) + wiki.setManager(self) + return wiki + + def removeWiki(self, wiki): + uid = self.getUid(wiki) + if uid in self.wikiUids: + self.wikiUids.remove(uid) + + def listWikis(self): + for uid in self.wikiUids: + yield self.getObject(uid) + + @Lazy + def wikiUids(self): + return self.context.getGenericAttribute('wikis') + + def getPlugins(self): + return self.context.getGenericAttribute('plugins') + + def getUid(self, obj): + return super(WikiManager, self).getUid(obj.context) + + def getObject(self, uid): + obj = self.resolveUid(uid) + if obj is None: + co = super(WikiManager, self).getObject(uid) + return co.typeInterface(co) + return obj + + +class Wiki(BaseWiki): + + adapts(IGenericFolder) + + def __init__(self, context): + self.context = context + + @property + def name(self): + return self.context.getId() + + @property + def pages(self): + # TODO: restrict to wiki page objects; use generic access methods + return dict((k, WikiPage(v)) for k, v in self.context.objectItems()) + + def createPage(self, name, title, text=u''): + obj = self.context.pageFactory(name) + page = WikiPage(obj) + page.title = title + page.text = text + self.context._setObject(name, obj) + return getattr(self.context, name) + + def getManager(self): + # TODO: fetch tool/utility in a generic way + co = self.context.portal_wikimanager + return co.typeInterface(co) + + def absolute_url(self): + return self.context.absolute_url() + + +class WikiPage(BaseWikiPage): + + adapts(IGenericFolder) + + def __init__(self, context): + self.context = context + + def getTitle(self): + return self.context.title + def setTitle(self, title): + self.context.title = title + title = property(getTitle, setTitle) + + def getText(self): + return self.context.text + def setText(self, text): + if self.context.getProperty('text') is None: + self.context.manage_addProperty('text', text, 'text') + else: + self.context.manage_changeProperties(id='text', value=text) + text = property(getText, setText) + + def getWiki(self): + # TODO: fetch wiki in a generic way + return Wiki(aq_parent(aq_inner(self.context))) + #return Wiki(getParent(self.context)) + + def absolute_url(self): + return self.context.absolute_url() + diff --git a/wiki/interfaces.py b/wiki/interfaces.py index 64a1ed7..c5ed295 100644 --- a/wiki/interfaces.py +++ b/wiki/interfaces.py @@ -23,6 +23,7 @@ $Id$ """ from zope.interface import Interface, Attribute +from zope import schema class IWikiConfigInfo(Interface): @@ -96,6 +97,8 @@ class IWiki(Interface): """ A collection of wiki pages, or - more generally - wiki components. """ + title = schema.TextLine(title=u'Title') + name = Attribute('The name or address of the wiki unique within the ' 'scope of the wiki manager.') pages = Attribute('') diff --git a/wiki/tests.py b/wiki/tests.py index d27e465..550032f 100755 --- a/wiki/tests.py +++ b/wiki/tests.py @@ -53,7 +53,7 @@ class Test(unittest.TestCase): def setUp(testCase): component.provideAdapter(WikiURL, (IWiki, IBrowserRequest), IAbsoluteURL) component.provideAdapter(PageURL, (IWikiPage, IBrowserRequest), IAbsoluteURL) - component.provideUtility(IntIdsStub()) + #component.provideUtility(IntIdsStub()) component.provideUtility(WikiConfiguration()) component.provideUtility(DocutilsHTMLWriter(), name='docutils.html') component.provideUtility(DocutilsRstxParser(), name='docutils.rstx') @@ -62,6 +62,8 @@ def setUp(testCase): component.provideAdapter(link.LinkManager) links = link.setupLinkManager(None) component.provideUtility(links, name='tracking') + from cybertools.wiki.generic import wiki + wiki.IntIds = IntIdsStub def test_suite(): diff --git a/wiki/tracking/link.py b/wiki/tracking/link.py index 3aa5521..7d33d0f 100644 --- a/wiki/tracking/link.py +++ b/wiki/tracking/link.py @@ -135,6 +135,7 @@ class LinkManager(BaseLinkManager): return self.context +# for testing only: def setupLinkManager(manager): ts = TrackingStorage(trackFactory=Link) return ILinkManager(ts) diff --git a/z2/README.txt b/z2/README.txt new file mode 100644 index 0000000..51f8a18 --- /dev/null +++ b/z2/README.txt @@ -0,0 +1,10 @@ +================================================================= +Supporting the Zope2 Environment for Zope3/ZTK-based Applications +================================================================= + + ($Id$) + + >>> from zope import component + >>> from zope.publisher.browser import TestRequest + + diff --git a/z2/__init__.py b/z2/__init__.py new file mode 100644 index 0000000..38314f3 --- /dev/null +++ b/z2/__init__.py @@ -0,0 +1,3 @@ +""" +$Id$ +""" diff --git a/z2/browser.py b/z2/browser.py new file mode 100644 index 0000000..d161617 --- /dev/null +++ b/z2/browser.py @@ -0,0 +1,48 @@ +# +# 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 +# + +""" +Base classes for views. + +$Id$ +""" + +from zope.cachedescriptors.property import Lazy +from zope import component +from Products.Five import BrowserView + + +class GenericView(BrowserView): + + name = 'index_html' + + @Lazy + def object(self): + return self.context.typeInterface(self.context) + + @Lazy + def objectView(self): + return component.getMultiAdapter((self.object, self.request), name=self.name) + + def __call__(self): + return self.objectView() + + +class GenericAddForm(GenericView): + + name = 'create.html' diff --git a/z2/configure.zcml b/z2/configure.zcml new file mode 100644 index 0000000..6d22a6b --- /dev/null +++ b/z2/configure.zcml @@ -0,0 +1,29 @@ + + + + + + + + + + + diff --git a/z2/generic.py b/z2/generic.py new file mode 100644 index 0000000..8a816ac --- /dev/null +++ b/z2/generic.py @@ -0,0 +1,75 @@ +# +# 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 +# + +""" +Base classes. + +$Id$ +""" + +from persistent.mapping import PersistentMapping +from zope.app.container.interfaces import IObjectAddedEvent +from zope import component +from zope.interface import implements + +from cybertools.util.generic.interfaces import IGeneric +from cybertools.util.generic.interfaces import IGenericObject, IGenericFolder + + +_not_found = object() + + +class GenericObject(object): + """ A mixin class supporting generic attribute access and other + basic or common functionality when combined with Zope2's + SimpleItem. + """ + + implements(IGenericObject) + + typeInterface = None + + def setup(self): + self.__generic_attributes__ = PersistentMapping() + if self.typeInterface: + obj = self.typeInterface(self) + obj.setup() + + def getGenericAttribute(self, attr, default=_not_found): + value = self.__generic_attributes__.get(attr, default) + if value is _not_found: + raise AttributeError(attr) + return value + + def setGenericAttribute(self, attr, value): + self.__generic_attributes__[attr] = value + return value + + +class GenericFolder(GenericObject): + """ Provide generic (i.e. dictionary-like) folder access to Zope2's + Folder or BTreeFolder. + """ + + implements(IGenericFolder) + + +@component.adapter(IGeneric, IObjectAddedEvent) +def setup(obj, event): + obj.setup() +component.provideHandler(setup) diff --git a/z2/tests.py b/z2/tests.py new file mode 100755 index 0000000..78feeeb --- /dev/null +++ b/z2/tests.py @@ -0,0 +1,28 @@ +#! /usr/bin/python + +""" +Tests for the 'cybertools.z2' package. + +$Id$ +""" + +import unittest, doctest +from zope.testing.doctestunit import DocFileSuite + + +class Test(unittest.TestCase): + "Basic tests for the wiki package." + + def testBasicStuff(self): + pass + + +def test_suite(): + flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS + return unittest.TestSuite(( + unittest.makeSuite(Test), + DocFileSuite('README.txt', optionflags=flags), + )) + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') diff --git a/z2/traversal.py b/z2/traversal.py new file mode 100644 index 0000000..fdb364b --- /dev/null +++ b/z2/traversal.py @@ -0,0 +1,45 @@ +# +# 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 +# +""" +IPublishTraverse adapter for Zope 2 objects providing IBase. + +$Id$ +""" + +from zope import component +from ZPublisher.BaseRequest import DefaultPublishTraverse +from ZPublisher.HTTPRequest import HTTPRequest + +from cybertools.util.generic.interfaces import IGeneric + + +class Traverser(DefaultPublishTraverse): + + def publishTraverse(self, request, name): + typeInterface = getattr(self.context, 'typeInterface', None) + if typeInterface is not None: + genObj = IGeneric(self.context, None) + if genObj is not None: + obj = typeInterface(genObj, None) + if obj is not None: + view = component.queryMultiAdapter((obj, request), name=name) + #print '*** obj', obj, view + if view is not None: + return view + return super(Traverser, self).publishTraverse(request, name) +