[z3-checkins] r5535 - in z3/pentecost: . branch tag trunk
philikon at codespeak.net
philikon at codespeak.net
Mon Jul 12 15:58:22 MEST 2004
Author: philikon
Date: Mon Jul 12 15:58:21 2004
New Revision: 5535
Added:
z3/pentecost/
z3/pentecost/branch/
z3/pentecost/tag/
z3/pentecost/trunk/
z3/pentecost/trunk/README.txt
z3/pentecost/trunk/TODO.txt
z3/pentecost/trunk/__init__.py
z3/pentecost/trunk/btreecontainer.zcml
z3/pentecost/trunk/configure.zcml
z3/pentecost/trunk/interfaces.py
z3/pentecost/trunk/pentecost-configure.zcml
z3/pentecost/trunk/pentecost.py
z3/pentecost/trunk/tests.py
z3/pentecost/trunk/traverse.py
z3/pentecost/trunk/vocabulary.py
Log:
Checked in a little piece of software that I hacked together
last weekend. Pentecost can be used to i18n-ize regular content
components by using masquerading containers. Any IContainer
implementation will do. I would be interested in feedback.
Added: z3/pentecost/trunk/README.txt
==============================================================================
--- (empty file)
+++ z3/pentecost/trunk/README.txt Mon Jul 12 15:58:21 2004
@@ -0,0 +1,61 @@
+==========
+README.txt
+==========
+
+``And they were all filled with the Holy Ghost, and began to speak
+with other tongues, as the Spirit gave them utterance.''
+
+ Acts 2:4
+
+
+Overview
+--------
+
+With Zope 3, it has become almost (almost!) trivial to make a web
+application i18n-aware. The ``utilities/i18nextract.py`` script
+extracts i18n message strings from Python code, Page Templates and
+ZCML files. If the message strings are only half decently tagged, the
+message catalog template can be handed to a translator and, voila!,
+you have a translated application.
+
+Yet, content often needs to be made available in different languages,
+too. Thanks to a simple language negotiator API, content objects that
+need to provide data in different languages can be created with
+limited, but still existant effort. This effort turns into an
+inconvenience when content components that provide the functionality
+you need already exist but lack i18n-awareness. You would now have to
+reimplement them.
+
+This is where Pentecost comes in. Instead of making content
+components i18n-aware, they are wrapped in a Pentecost object, which
+is really a container that can contain an instance of your content
+component for every language. To the outside, a Pentecost object
+tries to behave like your content component and always serves the
+version of your content component that the user prefers according to
+his/her language settings.
+
+Pentecost is to be designed to be as modular as possible. It works
+with
+
+ * any type of content component
+
+ * any container implementation (IContainer)
+
+That means, Pentecost can theoretically work with containers that
+store their data in different places than the ZODB, such as SQL. To
+make Pentecost work with your container, only ZCML configuration is
+needed, no Python code.
+
+Pentecost comes with a default container configuration using Zope's
+BTreeContainer in ``btreecontainer.zcml``. To keep Pentecost itself
+as independent as possible, it is not loaded by default. A good place
+to load it is in the ZCML slug that is placed in the
+``package-includes`` directory of your Zope instance.
+
+
+Copyright
+---------
+
+Pentecost is copyrighted (c) 2004 by Philipp "philiKON" von
+Weitershausen. It is freely distributable under the terms of the Zope
+Public License Version 2.1 (ZPL), as distributed with Zope 3.
Added: z3/pentecost/trunk/TODO.txt
==============================================================================
--- (empty file)
+++ z3/pentecost/trunk/TODO.txt Mon Jul 12 15:58:21 2004
@@ -0,0 +1,18 @@
+====
+TODO
+====
+
+- what about annotations, metadata, etc.?
+
+- functional tests
+
+- better contents, adding, etc. views --> browser subpackage
+
+ * default/fallback language should be settable
+
+ * allow a more comfortable way of adding languages (e.g. choose a
+ language from a list or something similar)
+
+ => need a language vocabulary?
+
+- browser:icon... get it from the current language object
Added: z3/pentecost/trunk/__init__.py
==============================================================================
--- (empty file)
+++ z3/pentecost/trunk/__init__.py Mon Jul 12 15:58:21 2004
@@ -0,0 +1 @@
+# make this directory a package
Added: z3/pentecost/trunk/btreecontainer.zcml
==============================================================================
--- (empty file)
+++ z3/pentecost/trunk/btreecontainer.zcml Mon Jul 12 15:58:21 2004
@@ -0,0 +1,50 @@
+<configure
+ xmlns="http://namespaces.zope.org/zope"
+ xmlns:browser="http://namespaces.zope.org/browser"
+ i18n_domain="pentecost"
+ >
+
+ <!-- Pentecost works with any IContainer;
+ This default implementation uses BTreeContainer -->
+
+ <!-- BTreeContainer doesn't have security declarations, so we
+ provide some here; when using Pentecost with your own
+ container, make sure it has the proper declarations, including
+ the one for IPentecost -->
+
+ <content class="zope.app.container.btree.BTreeContainer">
+ <require
+ permission="zope.View"
+ interface="zope.app.container.interfaces.IReadContainer"
+ />
+ <require
+ permission="zope.ManageContent"
+ interface="zope.app.container.interfaces.IWriteContainer"
+ />
+
+ <require
+ permission="zope.View"
+ interface="pentecost.interfaces.IPentecost"
+ />
+ <require
+ permission="zope.ManageContent"
+ set_schema="pentecost.interfaces.IPentecost"
+ />
+ </content>
+
+ <browser:addMenuItem
+ title="Pentecost"
+ class="zope.app.container.btree.BTreeContainer"
+ view="AddPentecost.html"
+ permission="zope.ManageContent"
+ />
+
+ <browser:addform
+ schema="pentecost.interfaces.IPentecostAdd"
+ content_factory="zope.app.container.btree.BTreeContainer"
+ label="Add Pentecost object"
+ name="AddPentecost.html"
+ permission="zope.ManageContent"
+ />
+
+</configure>
Added: z3/pentecost/trunk/configure.zcml
==============================================================================
--- (empty file)
+++ z3/pentecost/trunk/configure.zcml Mon Jul 12 15:58:21 2004
@@ -0,0 +1,64 @@
+<configure
+ xmlns="http://namespaces.zope.org/zope"
+ xmlns:browser="http://namespaces.zope.org/browser"
+ xmlns:i18n="http://namespaces.zope.org/i18n"
+ i18n_domain="pentecost"
+ >
+
+ <adapter
+ for="zope.app.container.interfaces.IContainer"
+ provides="pentecost.interfaces.IPentecostAdd"
+ factory="pentecost.pentecost.ContainerAddingAdapter"
+ />
+
+ <vocabulary
+ name="Add Menu Content Types"
+ factory="pentecost.vocabulary.AddMenuContentTypesVocabulary"
+ />
+
+ <content class="pentecost.pentecost.PentecostInterface">
+ <!-- for InterfaceClass, these security declarations are built
+ into zope.security; we have to do it through ZCML here -->
+ <allow interface="zope.interface.interfaces.IInterface" />
+ <allow attributes="__str__ _implied subscribe" />
+ </content>
+
+ <view
+ for="pentecost.interfaces.IPentecostContainer"
+ type="zope.publisher.interfaces.browser.IBrowserRequest"
+ provides="zope.publisher.interfaces.browser.IBrowserPublisher"
+ factory="pentecost.traverse.PentecostTraverser"
+ permission="zope.Public"
+ />
+
+ <view
+ for="pentecost.interfaces.IPentecostContainer"
+ type="zope.publisher.interfaces.browser.IBrowserRequest"
+ name="view"
+ provides="zope.app.traversing.interfaces.ITraversable"
+ factory="pentecost.traverse.PentecostViewTraverser"
+ />
+
+
+ <!-- TODO: provide different views for contents, adding etc. -->
+
+ <browser:page
+ for="pentecost.interfaces.IPentecostContainer"
+ name="contents.html"
+ class="zope.app.container.browser.contents.Contents"
+ attribute="contents"
+ menu="zmi_views" title="Contents"
+ permission="zope.ManageContent"
+ />
+
+ <configure package="zope.app.preview">
+ <browser:page
+ for="pentecost.interfaces.IPentecostContainer"
+ name="preview.html"
+ template="preview.pt"
+ menu="zmi_views" title="Preview"
+ permission="zope.ManageContent"
+ />
+ </configure>
+
+</configure>
Added: z3/pentecost/trunk/interfaces.py
==============================================================================
--- (empty file)
+++ z3/pentecost/trunk/interfaces.py Mon Jul 12 15:58:21 2004
@@ -0,0 +1,52 @@
+##############################################################################
+#
+# Copyright (c) 2004 Philipp "philiKON" von Weitershausen
+#
+# This software is distributed under the terms of the Zope Public
+# License (ZPL) v2.1.
+#
+##############################################################################
+"""Interfaces
+
+$Id$
+"""
+from zope.interface import Interface
+from zope.schema import Choice
+from zope.i18nmessageid import MessageIDFactory
+from zope.app.container.interfaces import IContentContainer
+
+_ = MessageIDFactory('pentecost')
+
+class IPentecost(Interface):
+ """Describes Pentecost's dependency on a specific content type"""
+
+ # Instead of this custom vocabulary 'Add Menu Content Types',
+ # which constructs a list of content types from the browser
+ # container add menu, you can also use the regular 'Content Types'
+ # vocabulary. With this one, however, the term's titles for the
+ # add form are the interface's dotted names which isn't as nice as
+ # the titles of the add menu entries.
+ # ('zope.app.file.interfaces.IFile' vs. 'File')
+
+ content_type = Choice(
+ title=_(u"Content type"),
+ description=_(u"The content type of subobjects"),
+ required=True,
+ vocabulary="Add Menu Content Types",
+ )
+
+class IPentecostContainer(IPentecost, IContentContainer):
+ """Pentecost container
+
+ Marker interface to identify pentecost containers so that we can
+ register our own traversal adapters for them.
+ """
+
+class IPentecostAdd(IPentecost):
+ """Pentecost adding
+
+ Describes the data necessary for adding a pentecost object. We
+ define another schema here on purpose because we want the form
+ machinery to adapt newly added containers so that we can make
+ Pentecost containers out of them during adaption.
+ """
Added: z3/pentecost/trunk/pentecost-configure.zcml
==============================================================================
--- (empty file)
+++ z3/pentecost/trunk/pentecost-configure.zcml Mon Jul 12 15:58:21 2004
@@ -0,0 +1,4 @@
+<configure>
+ <include package="pentecost" />
+ <include package="pentecost" file="btreecontainer.zcml" />
+</configure>
Added: z3/pentecost/trunk/pentecost.py
==============================================================================
--- (empty file)
+++ z3/pentecost/trunk/pentecost.py Mon Jul 12 15:58:21 2004
@@ -0,0 +1,160 @@
+##############################################################################
+#
+# Copyright (c) 2004 Philipp "philiKON" von Weitershausen
+#
+# This software is distributed under the terms of the Zope Public
+# License (ZPL) v2.1.
+#
+##############################################################################
+"""Core machinery
+
+The components in here allow Pentecost to be as transparent as it is.
+It works with any IContainer and any content type.
+
+$Id$
+"""
+from zope.interface import implements, directlyProvides
+from zope.interface.interface import InterfaceClass
+from zope.security.proxy import trustedRemoveSecurityProxy
+
+from zope.app import zapi
+from zope.app.container.interfaces import IContainer
+from zope.app.container.constraints import ItemTypePrecondition
+from zope.app.content.interfaces import IContentType
+from zope.app.component.interface import provideInterface
+
+from interfaces import IPentecostAdd, IPentecostContainer
+
+class PentecostInterface(InterfaceClass):
+ """Pickable interface for pentecost objects
+
+ Pentecost creates interfaces on-the-fly, thus the pickling
+ machinery cannot rely on finding it there again and wants to
+ pickle it. However, interfaces are not pickable (yet), so we do
+ some tricks to recreate on-the-fly interfaces upon demand. Plus,
+ we can hide some functionality here.
+
+ For example PentecostInterfaces always extend
+ ``IPentecostContainer``:
+
+ >>> from zope.interface import Interface
+ >>> IFoo = PentecostInterface('IFoo', Interface)
+ >>> IFoo.extends(IPentecostContainer)
+ True
+
+ By implementing a method of the pickling protocol, they avoid pickling:
+
+ >>> reduced = IFoo.__reduce__()[1]
+ >>> reduced[0]
+ 'IFoo'
+ >>> reduced[1] is Interface
+ True
+
+ Finally, to play nice, they are hasheable:
+
+ >>> hash(IFoo) == hash('IFoo')
+ True
+
+ XXX test precondition on __setitem__
+ """
+
+ def __init__(self, name, content_type):
+ self.content_type = content_type
+
+ class Base(IPentecostContainer):
+ def __setitem__(name, object):
+ """Add an item"""
+ __setitem__.precondition = ItemTypePrecondition(content_type)
+
+ super(PentecostInterface, self).__init__(name, (Base,))
+
+ def __reduce__(self):
+ """Tell the pickling machiner not to pickle a PentecostInterface but
+ to recreate instances with whatever we return here"""
+ # That we return content_type here implies that it needs to be
+ # located in a module like a regular interface, a risk that
+ # we're willing to take for now
+ return (PentecostInterface, (self.getName(), self.content_type))
+
+ def __hash__(self):
+ return hash(self.__name__)
+
+class ContainerAddingAdapter(object):
+ """Adapter for young, innocent IContainers that are to be transvestited
+ to pentecost objects.
+
+ First, we need a content type that we want our pentecost object to
+ contain and an object implementing it:
+
+ >>> from zope.interface import implements, providedBy, Interface
+ >>> class ISampleType(Interface):
+ ... pass
+
+ >>> class SampleObject(object):
+ ... implements(ISampleType)
+ >>> sample = SampleObject()
+
+ Let's create a container then. This poor bastard doesn't know yet
+ what's gonna hit him... Muahaha!
+
+ >>> from zope.app.container.sample import SampleContainer
+ >>> container = SampleContainer()
+
+ See, this container does not have a 2nd bottom or anything. It's
+ a regular container:
+
+ >>> IPentecostContainer.providedBy(container)
+ False
+ >>> from zope.app.container.constraints import checkObject
+ >>> checkObject(container, "some_obj", object())
+
+ As you can see, no constraint on it yet. Now to begin the delicate
+ operation... We adapt the container and set the content type.
+
+ >>> adapter = ContainerAddingAdapter(container)
+ >>> adapter.content_type = ISampleType
+
+ It should now have a constraint that only allows us to add
+ ISampleTypes to it:
+
+ >>> checkObject(container, "sample", sample)
+
+ the simple object from before fails to be added:
+
+ >>> from zope.app.container.interfaces import InvalidItemType
+ >>> try:
+ ... checkObject(container, "some_obj", object())
+ ... except InvalidItemType:
+ ... print 'Failed'
+ ... else:
+ ... print 'Should have failed'
+ Failed
+ """
+ implements(IPentecostAdd)
+ __used_for__ = IContainer
+
+ def __init__(self, context):
+ self.context = context
+
+ def getContentType(self):
+ return self.context.content_type
+
+ def setContentType(self, content_type):
+ if IPentecostContainer.providedBy(self.context):
+ # XXX shall we raise an error here?
+ return
+
+ self.context.content_type = content_type
+
+ # create a custom interface based on the content type for the
+ # pentecost object and make it a content type
+ # TODO: maybe do something about unique names
+ name = "Pentecost"
+ iface = PentecostInterface(name, content_type)
+ directlyProvides(iface, IContentType)
+
+ # directlyProvides freaks out about a security proxy
+ context = trustedRemoveSecurityProxy(self.context)
+ directlyProvides(context, iface)
+
+ content_type = property(getContentType, setContentType)
Added: z3/pentecost/trunk/tests.py
==============================================================================
--- (empty file)
+++ z3/pentecost/trunk/tests.py Mon Jul 12 15:58:21 2004
@@ -0,0 +1,23 @@
+##############################################################################
+#
+# Copyright (c) 2004 Philipp "philiKON" von Weitershausen
+#
+# This software is distributed under the terms of the Zope Public
+# License (ZPL) v2.1.
+#
+##############################################################################
+"""Unit tests
+
+$Id$
+"""
+import unittest
+from zope.testing.doctestunit import DocTestSuite
+
+def test_suite():
+ return unittest.TestSuite((
+ DocTestSuite('pentecost.pentecost'),
+ DocTestSuite('pentecost.vocabulary'),
+ ))
+
+if __name__ == '__main__':
+ unittest.main()
Added: z3/pentecost/trunk/traverse.py
==============================================================================
--- (empty file)
+++ z3/pentecost/trunk/traverse.py Mon Jul 12 15:58:21 2004
@@ -0,0 +1,67 @@
+##############################################################################
+#
+# Copyright (c) 2004 Philipp "philiKON" von Weitershausen
+#
+# This software is distributed under the terms of the Zope Public
+# License (ZPL) v2.1.
+#
+##############################################################################
+"""Browser traversal adapters
+
+These make it possible to simply open the URL of a pentecost object in
+a browser and get whatever language back the browser prefers.
+
+$Id$
+"""
+from zope.interface import implements
+from zope.i18n.negotiator import Negotiator
+from zope.exceptions import NotFoundError
+from zope.publisher.interfaces.browser import IBrowserPublisher
+
+from zope.app import zapi
+from zope.app.container.traversal import ContainerTraverser
+from zope.app.traversing.interfaces import ITraversable
+
+from interfaces import IPentecostContainer
+
+class PentecostTraverser(ContainerTraverser, object):
+ """A traverser that knows how to look up objects by name in a container."""
+
+ implements(IBrowserPublisher)
+ __used_for__ = IPentecostContainer
+
+ def browserDefault(self, request):
+ """See zope.publisher.browser.interfaces.IBrowserPublisher"""
+ negotiator = Negotiator()
+ language = negotiator.getLanguage(self.context.keys(), self.request)
+ if language in self.context:
+ subobj = self.context.get(language)
+ return self.context, (language,)
+
+ return super(PentecostTraverser, self).browserDefault(request)
+
+class PentecostViewTraverser(object):
+ """Handle views for Pentecost containers"""
+ implements(ITraversable)
+ __used_for__ = IPentecostContainer
+
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+
+ def traverse(self, name, ignored):
+ # find our own views first
+ view = zapi.queryView(self.context, name, self.request)
+ if view is not None:
+ return view
+
+ # maybe the language subobject has a view...
+ negotiator = Negotiator()
+ language = negotiator.getLanguage(self.context.keys(), self.request)
+ subobj = self.context.get(language, None)
+ view = zapi.queryView(subobj, name, self.request)
+
+ # or neither, then report not found
+ if view is None:
+ raise NotFoundError(self.context, name)
+ return view
Added: z3/pentecost/trunk/vocabulary.py
==============================================================================
--- (empty file)
+++ z3/pentecost/trunk/vocabulary.py Mon Jul 12 15:58:21 2004
@@ -0,0 +1,88 @@
+##############################################################################
+#
+# Copyright (c) 2004 Philipp "philiKON" von Weitershausen
+#
+# This software is distributed under the terms of the Zope Public
+# License (ZPL) v2.1.
+#
+##############################################################################
+"""Vocabulary
+
+$Id$
+"""
+from zope.interface.interface import Specification
+from zope.component.interfaces import IFactory
+from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary
+
+from zope.app import zapi
+from zope.app.content.interfaces import IContentType
+from zope.app.container.constraints import checkFactory
+
+def queryContentType(ifaces):
+ """Out of an iterable of interfaces, find the one interface that is a
+ content type
+
+ Imagine we have a couple of interfaces, one of them is a content
+ type:
+
+ >>> from zope.interface import Interface, directlyProvides
+ >>> class IFoo(Interface):
+ ... pass
+ >>> class IBar(Interface):
+ ... pass
+ >>> directlyProvides(IBar, IContentType)
+ >>> class ISubbar(IBar):
+ ... pass
+
+ Now we throw two of them in a list:
+
+ >>> ifaces = [IFoo, ISubbar]
+
+ We expect queryContentType to tell us that IBar is the content
+ type:
+
+ >>> queryContentType(ifaces) is IBar
+ True
+ """
+ spec = Specification(ifaces)
+ for iface in spec.__iro__:
+ if IContentType.providedBy(iface):
+ return iface
+ return None
+
+class AddMenuContentTypesVocabulary(SimpleVocabulary):
+ """Vocabulary of content type interfaces based on the browser add menu
+
+ TODO: doctest
+ """
+
+ def __init__(self, context):
+ terms = []
+ menu_service = zapi.getService("BrowserMenu")
+ menu_id = 'zope.app.container.add'
+ # there can be several factories for one content type; we
+ # really care about the content type, not the factory; we just
+ # conveniently use the factory id as a token
+ seen_types = {}
+ for item in menu_service.getAllMenuItems(menu_id, context):
+ if item.extra:
+ token = item.extra.get('factory')
+ if token:
+ factory = zapi.getUtility(IFactory, token)
+ content_type = queryContentType(factory.getInterfaces())
+ # don't continue if the add entry is not for a content type
+ if not content_type:
+ continue
+ # check if this type of object can actually added here
+ if not checkFactory(context, None, factory):
+ continue
+ if content_type in seen_types:
+ # XXX this is not very i18n friendly
+ seen_types[content_type][1] += ", " + item.title
+ else:
+ seen_types[content_type] = [token, item.title]
+
+ terms = [SimpleTerm(content_type, token, title)
+ for content_type, (token, title)
+ in seen_types.iteritems()]
+ super(AddMenuContentTypesVocabulary, self).__init__(terms)
More information about the z3-checkins
mailing list