Commit 83c583ac authored by Godefroid Chapelle's avatar Godefroid Chapelle

importing actual copy of Five zope28-integration branch

parent 493cec6b
Five Changes
============
Five 0.3 (2005-03-11)
---------------------
* Five now uses the Zope 2 page template engine, not the Zope 3
engine. This allows better integration with Zope 2-based page
templates, such as macros.
It uses TrustedExecutables technology (thanks to Dieter Maurer) to
turn off Zope 2 security in page templates, so Five's security
behavior is very similar to what it was before.
* Five now supports the browser:menu, menuItem and menuItems
directives.
* A new Five-specific directive has been added:
five:pagesFromDirectory. This adds one page for each .pt file in a
directory to the specified interface. This is useful for Five
integration with CMF and other systems that have Page Templates
macros that need to be shared between Zope2 and Five.
* Five.security.checkPermission has been changed from a (unused)
method for checking the existence of permissions. Use
zope.app.security.permission.checkPermission if you need that
functionality.
Instead Five.security.checkPermission is now a Five version of
zope.security.checkPermission, which checks if the current user has
a permission on an object.
* Support for browser:editform. You can now use schemas for editing.
* Support for browser:addform; add forms using '+'. You can now browse
to 'container/+/addsomething.html' to get to a schema-driven add
form.
* Fixed a traversal bug which caused Zope to give the wrong error when
a page could not be found (missing docstring instead of not
found). Zope 2.7.4 (or higher) is required for this fix.
Five 0.2b (2004-09-24)
----------------------
* Added utility module, 'bridge', allowing reuse of Zope 2 interfaces
(by introspecting them to create equivalent Zope 3 interfaces).
* five:viewable was renamed to five:traversable, five:viewable still
works but is deprecated; a deprecation warning is emitted when it is
used.
* like in Zope3, an ITraverser adapter is looked up to determine what
happens when traversing into a Five traversable object.
* added five:defaultViewable to make instances of a class directly
viewable using browser:defaultView. This is hookable by the use of a
IBrowserDefault adapter
* deprecated use of Products.Five.api as public API for other products
to use, instead import directly from Products.Five. Retired
Traversable and Viewable from the public API; use ZCML directives
(five:traversable, five:defaultView) instead of mixins to make
instances of classes work with Five.
* classes that Five monkeypatches now have a __five_method__
attribute, making it easier for Five not to stomp on existing methods.
* registered absolute_url view and IAbsoluteURL adapter for *
* zope.app.traversing is registered by default, to make special
namespaces available (eg: @@, ++resource++)
* we now have resources (FileResource, ImageResource,
PageTemplateResource) and directory resources.
* Zope 3 'StandardMacros' now works with Five as well.
* browser:page now correctly handles the allow_attributes and protects
the named attributes on the view with the same permission used for
the view.
* zopeconf.py will try to find etc/zope.conf on INSTANCE_HOME. This
requires Zope 2.7.2, as earlier Zope versions have a bug in this
area which causes them to look in lib/python/Testing.
* Exposed the Zope 3 event system to Five. A class can be made to send
out event notifications using the five:sendEvents directive. Events can
be subscribed to using the subscriber directive.
* Change in findProducts so that non-filesystem products are skipped.
Five 0.1 (2004-07-30)
---------------------
Initial public release (mainly Martijn's work)
Five is distributed under the provisions of the Zope Public License
(ZPL) v2.1. See doc/ZopePublicLicense.txt for the license text.
Copyright (C) 2005 Five Contributors. See CREDITS.txt for a list of
Five contributors.
Five contains source code derived from:
- Zope 3, copyright (C) 2001-2005 by Zope Corporation. Code that
falls under this copyright is prefixed with the following header:
Copyright (c) 2001-2004 Zope Corporation and Contributors.
All Rights Reserved.
This software is subject to the provisions of the Zope Public
License, Version 2.1 (ZPL). A copy of the ZPL should accompany this
distribution. THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL
EXPRESS OR IMPLIED WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY,
AGAINST INFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE.
- metaclass.py is derived from PEAK, copyright (C) 1996-2004 by
Phillip J. Eby and Tyler C. Sarna. PEAK may be used under the same
terms as Zope.
- TrustedExecutables. Dieter Mauer kindly allow licensing this under the
ZPL 2.1.
\ No newline at end of file
Five contributors
-----------------
- Martijn Faassen (faassen@infrae.com)
- Sidnei da Silva (sidnei@awkly.org)
- Philipp von Weitershausen (philikon@philikon.de)
- Lennart Regebro (regebro@nuxeo.com)
- Tres Seaver (tres@zope.com)
- Andy Adiwidjaja (mail@adiwidjaja.com)
- Stuart Bishop (stuart@stuartbishop.net)
- Simon Eisenmann (simon@struktur.de)
- Dieter Maurer (dieter@handshake.de)
Thank you
---------
Infrae for the initial development and continuing support.
Martijn Faassen would like to thank ETH Zurich for their support and
encouragement during the initial development of Five.
Nuxeo for significant contributions to making Five usable in the real
world.
Dieter Maurer for use of code from TrustedExecutables within Five
under the ZPL.
The Five developers would like to thank the Zope 3 developers, in
particular Jim Fulton, for the mountain to stand on.
How to install Five
-------------------
Requirements for Five 0.3
=========================
* Zope 2.7.4+ with python 2.3.x. Zope versions lower than Zope 2.7.4
may work, but no guarantees.
* Zope X3.0.0, found here: http://zope.org/Products/ZopeX3/3.0.0final/
Installing Five
===============
Installing Five is relatively straightforward.
* Select a Zope 2.7 instance.
* Download and install Zope X3.0.0. You can get it compiled and
installed by:
* Typing ``configure``. When you're experimenting, typically you
want to use the ``--prefix`` directive to install the binaries
to install it somewhere in your homedirectory.
* ``make``
* ``make install``
On windows you can install choose to use the binary release instead.
Alternatively you can check out the latest subversion version of
Zope X3.0 and typing ``make`` to produce it in-place.
* You need to make your Zope 2.7 instance aware of Zope 3 so it can
import the ``zope``, ``persistent`` and ``transaction`` packages from it.
* In non-ZEO setups, you can simply go to the ``etc/zope.conf`` of
your Zope 2.7 instance and add a ``path`` entry. If you used the
released version of Zope X3.0.0, use something like the following::
path /path/to/installed/Zope3/lib/python
If you are instead using the subversion version, use::
path /path/to/Zope3/src
If you have problems however, see the instructions for the ZEO
setup.
* In ZEO setups (or some other circumstances), Zope 3's ZEO packages
will interfere with Zope 2's. In this case you can create a new
directory, symlink the ``zope``, ``persistent`` and ``transaction``
packages in it and use this directory for the ``path`` entry in the
``etc/zope.conf`` of your Zope 2.7 instance.
* Next, install the Five product into your Zope 2.7 instance as a
product and restart Zope. Five should now be installed.
* You can also install various products in the ``demo`` subdirectory
of Five by copying them into your ``Products`` directory. In
addition, you can look at tests/products/FiveTest, which is a
product used for the Five tests, and may contain more recent
examples.
Installing the tests
====================
For information on how to install the automatic Five tests, please see
``tests/README.txt``.
Introduction
------------
"It was the dawn of the third age of Zope. The Five project was a dream
given form. Its goal: to use Zope 3 technologies in Zope 2.7 by
creating a Zope 2 product where Zope 3 and Zope 2 could work out their
differences peacefully." -- Babylon 5, creatively quoted
"The Law of Fives states simply that: ALL THINGS HAPPEN IN FIVES, OR
ARE DIVISIBLE BY OR ARE MULTIPLES OF FIVE, OR ARE SOMEHOW DIRECTLY OR
INDIRECTLY RELATED TO FIVE.
THE LAW OF FIVES IS NEVER WRONG." -- Principia Discordia
What is Five?
-------------
The goal of five is to allow Zope 2 developers to use Zope 3
technology right now, inside of Zope 2. Additionally, this allows a
gradual evolution of Zope 2 code to Zope 3.
Five already makes the following Zope 3 technologies available in Zope
2:
* Zope 3 interfaces
* ZCML (Zope Configuration Markup Language)
* Adapters
* Zope 3 views, even for standard Zope objects
* layers & skins
* schema/forms machinery, including edit and add forms.
* Zope 2 security declarations in ZCML instead of in Python code.
Together with another product, CMFonFive, Five can integrate into CMF.
For more information, see ``doc/features.txt``.
How to install Five
-------------------
See ``INSTALL.txt``.
How to use Five
---------------
Please see ``doc/manual.txt``.
# Copyright (C) 2004 by Dr. Dieter Maurer, Eichendorffstr. 23, D-66386 St. Ingbert, Germany
from new import function
def rebindFunction(f,rebindDir=None,**rebinds):
'''return *f* with some globals rebound.'''
d= {}
if rebindDir : d.update(rebindDir)
if rebinds: d.update(rebinds)
if not d: return f
f= getattr(f,'im_func',f)
fd= f.func_globals.copy()
fd.update(d)
nf= function(f.func_code,fd,f.func_name,f.func_defaults or ())
nf.__doc__= f.__doc__
if f.__dict__ is not None: nf.__dict__= f.__dict__.copy()
return nf
====
TODO
====
- more extensive testing whether event system works with things like Zope 2
folders etc
- ensuring that the event-sending behavior is as close to Zope 3's as
possible. A lot of edge cases with different behavior likely remain,
and things like IObjectModifiedEvents are not sent yet for folders.
- allow the multiple use of five:sendEvents
- allow Zope2 boilerplate context.registerClass be configured through zcml
- Figure out where add-view redirects should go.
- Instructions on using add views.
# Copyright (C) 2004 by Dr. Dieter Maurer, Eichendorffstr. 23, D-66386 St. Ingbert, Germany
from sys import modules
from Products.PageTemplates.PythonExpr import PythonExpr
from Products.PageTemplates.Expressions import \
SubPathExpr, PathExpr, \
StringExpr, \
getEngine, installHandlers
from ReuseUtils import rebindFunction
class _ModuleImporter:
def __getitem__(self, module):
__import__(module)
return modules[module]
ModuleImporter = _ModuleImporter()
def trustedTraverse(ob, path, ignored,):
if not path: return self
get = getattr
has = hasattr
N = None
M = rebindFunction # artifical marker
if isinstance(path, str): path = path.split('/')
else: path=list(path)
REQUEST={'TraversalRequestNameStack': path}
path.reverse()
pop=path.pop
if len(path) > 1 and not path[0]:
# Remove trailing slash
path.pop(0)
if not path[-1]:
# If the path starts with an empty string, go to the root first.
pop()
self=ob.getPhysicalRoot()
object = ob
while path:
name=pop()
__traceback_info__ = path, name
if name == '..':
o=getattr(object, 'aq_parent', M)
if o is not M:
object=o
continue
t=get(object, '__bobo_traverse__', M)
if t is not M: o=t(REQUEST, name)
else:
o = get(object, name, M)
if o is M:
try: o = object[name]
except AttributeError: # better exception
raise AttributeError(name)
object = o
return object
class SubPathExpr(SubPathExpr):
_eval = rebindFunction(SubPathExpr._eval.im_func,
restrictedTraverse=trustedTraverse,
)
class PathExpr(PathExpr):
__init__ = rebindFunction(PathExpr.__init__.im_func,
SubPathExpr=SubPathExpr,
)
class StringExpr(StringExpr):
__init__ = rebindFunction(StringExpr.__init__.im_func,
PathExpr=PathExpr,
)
installHandlers = rebindFunction(installHandlers,
PathExpr=PathExpr,
StringExpr=StringExpr,
PythonExpr=PythonExpr,
)
_engine=None
getEngine = rebindFunction(getEngine,
_engine=_engine,
installHandlers=installHandlers
)
##############################################################################
#
# Copyright (c) 2004 Five Contributors. All rights reserved.
#
# This software is distributed under the terms of the Zope Public
# License (ZPL) v2.1. See COPYING.txt for more information.
#
##############################################################################
"""Initialize the Five product
$Id: __init__.py 9796 2005-03-15 14:58:39Z efge $
"""
import Acquisition
from Globals import INSTANCE_HOME
import zcml
# public API provided by Five
# usage: from Products.Five import <something>
from browser import BrowserView, StandardMacros
def initialize(context):
zcml.load_site()
<html metal:use-macro="here/five_template/macros/master">
<body>
<div metal:fill-slot="main">
<div metal:define-macro="addform">
<form action="." tal:attributes="action request/URL" method="post"
enctype="multipart/form-data">
<div metal:define-macro="formbody">
<h3 tal:condition="view/label"
tal:content="view/label"
metal:define-slot="heading"
>Edit something</h3>
<p tal:define="status view/update"
tal:condition="status"
tal:content="status" />
<p tal:condition="view/errors" i18n:translate="">
There are <strong tal:content="python:len(view.errors)"
i18n:name="num_errors">6</strong> input errors.
</p>
<div metal:define-slot="extra_info" tal:replace="nothing">
</div>
<div class="row" metal:define-slot="extra_top" tal:replace="nothing">
<div class="label">Extra top</div>
<div class="label"><input type="text" style="width:100%" /></div>
</div>
<div metal:use-macro="context/@@widget_macros/widget_rows" />
<div class="separator"></div>
<div class="row"
metal:define-slot="extra_bottom" tal:replace="nothing">
<div class="label">Extra bottom</div>
<div class="field"><input type="text" style="width:100%" /></div>
</div>
<div class="separator"></div>
</div>
<br/><br/>
<div class="row">
<div class="controls"><hr />
<input type='submit' value='Refresh'
i18n:attributes='value refresh-button' />
<input type='submit' value='Add' name='UPDATE_SUBMIT'
i18n:attributes='value add-button' />
<span tal:condition="context/nameAllowed|nothing" tal:omit-tag="">
&nbsp;&nbsp;<b i18n:translate="">Object Name</b>&nbsp;&nbsp;
<input type='text' name='add_input_name'
tal:attributes="value context/contentName" />
</span>
</div>
</div>
<div class="row" metal:define-slot="extra_buttons" tal:replace="nothing">
</div>
<div class="separator"></div>
</form>
</div>
</div>
</body>
</html>
<html metal:use-macro="here/five_template/macros/master">
<body>
<div metal:fill-slot="main">
<p>+ screen not yet supported by Five</p>
</div>
</body>
</html>
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Adding View
The Adding View is used to add new objects to a container. It is sort of a
factory screen.
"""
__docformat__ = 'restructuredtext'
from warnings import warn
from zope.interface import implements
from zope.publisher.interfaces import IPublishTraverse
from zope.component.interfaces import IFactory
from zope.app.exception.interfaces import UserError
from zope.app.container.interfaces import IAdding, INameChooser
from zope.app.container.interfaces import IContainerNamesContainer
from zope.app.container.constraints import checkFactory, checkObject
from zope.app import zapi
from zope.app.event.objectevent import ObjectCreatedEvent
from zope.event import notify
from zExceptions import BadRequest
from Products.Five import BrowserView
from Products.Five.traversable import Traversable
from Products.Five.pagetemplatefile import ZopeTwoPageTemplateFile
from Acquisition import Implicit
from OFS.SimpleItem import SimpleItem
class BasicAdding(Implicit, BrowserView):
implements(IAdding, IPublishTraverse)
def add(self, content):
"""See zope.app.container.interfaces.IAdding
"""
container = self.context
name = self.contentName
chooser = INameChooser(container)
# check precondition
checkObject(container, name, content)
if IContainerNamesContainer.providedBy(container):
# The container picks it's own names.
# We need to ask it to pick one.
name = chooser.chooseName(self.contentName or '', content)
else:
request = self.request
name = request.get('add_input_name', name)
if name is None:
name = chooser.chooseName(self.contentName or '', content)
elif name == '':
name = chooser.chooseName('', content)
chooser.checkName(name, container)
content.id = name
container._setObject(name, content)
self.contentName = name # Set the added object Name
return container._getOb(name)
contentName = None # usually set by Adding traverser
def nextURL(self):
"""See zope.app.container.interfaces.IAdding"""
# XXX this is definitely not right for all or even most uses
# of Five, but can be overridden by an AddView subclass, using
# the class attribute of a zcml:addform directive
return (str(zapi.getView(self.context, "absolute_url", self.request))
+ '/manage_main')
# set in BrowserView.__init__
request = None
context = None
def renderAddButton(self):
warn("The renderAddButton method is deprecated, use nameAllowed",
DeprecationWarning, 2)
def publishTraverse(self, request, name):
"""See zope.app.container.interfaces.IAdding"""
if '=' in name:
view_name, content_name = name.split("=", 1)
self.contentName = content_name
if view_name.startswith('@@'):
view_name = view_name[2:]
return zapi.getView(self, view_name, request)
if name.startswith('@@'):
view_name = name[2:]
else:
view_name = name
view = zapi.queryView(self, view_name, request)
if view is not None:
return view
factory = zapi.queryUtility(IFactory, name)
if factory is None:
return super(BasicAdding, self).publishTraverse(request, name)
return factory
def action(self, type_name='', id=''):
if not type_name:
raise UserError("You must select the type of object to add.")
if type_name.startswith('@@'):
type_name = type_name[2:]
if '/' in type_name:
view_name = type_name.split('/', 1)[0]
else:
view_name = type_name
if zapi.queryView(self, view_name, self.request) is not None:
url = "%s/%s=%s" % (
zapi.getView(self, "absolute_url", self.request),
type_name, id)
self.request.response.redirect(url)
return
if not self.contentName:
self.contentName = id
factory = zapi.getUtility(IFactory, type_name)
content = factory()
notify(ObjectCreatedEvent(content))
self.add(content)
self.request.response.redirect(self.nextURL())
def namesAccepted(self):
return not IContainerNamesContainer.providedBy(self.context)
def nameAllowed(self):
"""Return whether names can be input by the user."""
return not IContainerNamesContainer.providedBy(self.context)
class Adding(BasicAdding):
menu_id = None
index = ZopeTwoPageTemplateFile("adding.pt")
def addingInfo(self):
"""Return menu data.
This is sorted by title.
"""
container = self.context
menu_service = zapi.getService("BrowserMenu")
result = []
for menu_id in (self.menu_id, 'zope.app.container.add'):
if not menu_id:
continue
for item in menu_service.getMenu(menu_id, self, self.request):
extra = item.get('extra')
if extra:
factory = extra.get('factory')
if factory:
factory = zapi.getUtility(IFactory, factory)
if not checkFactory(container, None, factory):
continue
elif item['extra']['factory'] != item['action']:
item['has_custom_add_view']=True
result.append(item)
result.sort(lambda a, b: cmp(a['title'], b['title']))
return result
def isSingleMenuItem(self):
"Return whether there is single menu item or not."
return len(self.addingInfo()) == 1
def hasCustomAddView(self):
"This should be called only if there is `singleMenuItem` else return 0"
if self.isSingleMenuItem():
menu_item = self.addingInfo()[0]
if 'has_custom_add_view' in menu_item:
return True
return False
class ContentAdding(Adding, Traversable, SimpleItem):
menu_id = "add_content"
class ObjectManagerNameChooser:
"""A name chooser for a Zope object manager.
"""
implements(INameChooser)
def __init__(self, context):
self.context = context
def checkName(self, name, object):
try:
self.context._checkId(name, allow_dup=False)
except BadRequest:
raise UserError, "Id is in use or invalid"
def chooseName(self, name, object):
if not name:
name = object.__class__.__name__
dot = name.rfind('.')
if dot >= 0:
suffix = name[dot:]
name = name[:dot]
else:
suffix = ''
n = name + suffix
i = 1
while True:
try:
container._getOb(n)
except AttributeError:
pass
else:
break
i += 1
n = name + '-' + str(i) + suffix
# Make sure tha name is valid. We may have started with something bad.
self.checkName(n, object)
return n
##############################################################################
#
# Copyright (c) 2004 Five Contributors. All rights reserved.
#
# This software is distributed under the terms of the Zope Public
# License (ZPL) v2.1. See COPYING.txt for more information.
#
##############################################################################
"""Convenience package for short imports
$Id: api.py 6174 2004-08-25 17:19:28Z faassen $
"""
import warnings
warnings.warn('The use of the Products.Five.api module has been deprecated. '
'Import directly from Products.Five instead for public API.',
DeprecationWarning)
from browser import BrowserView, StandardMacros
from traversable import Traversable
from viewable import Viewable
##############################################################################
#
# Copyright (c) 2004 Five Contributors. All rights reserved.
#
# This software is distributed under the terms of the Zope Public
# License (ZPL) v2.1. See COPYING.txt for more information.
#
##############################################################################
""" Z2 -> Z3 bridge utilities.
$Id$
"""
from Interface._InterfaceClass import Interface as Z2_InterfaceClass
from Interface import Interface as Z2_Interface
from Interface import Attribute as Z2_Attribute
from zope.interface.interface import InterfaceClass as Z3_InterfaceClass
from zope.interface.interface import Interface as Z3_Interface
from zope.interface.interface import Attribute as Z3_Attribute
def fromZ2Interface(z2i):
""" Return a Zope 3 interface corresponding to 'z2i'.
o 'z2i' must be a Zope 2 interface.
"""
if not isinstance(z2i, Z2_InterfaceClass):
raise ValueError, 'Not a Zope 2 interface!'
if z2i is Z2_Interface: # special case; root in new hierarchy!
return Z3_Interface
name = z2i.getName()
bases = [ fromZ2Interface(x) for x in z2i.getBases() ]
attrs = {}
for k, v in z2i.namesAndDescriptions():
if isinstance(v, Z2_Attribute):
v = fromZ2Attribute(v)
attrs[k] = v
# XXX: Note that we pass the original interface's __module__;
# we may live to regret that.
return Z3_InterfaceClass(name=name,
bases=bases,
attrs=attrs,
__doc__=z2i.getDoc(),
__module__=z2i.__module__,
)
def fromZ2Attribute(z2a):
""" Return a Zope 3 interface attribute corresponding to 'z2a'.
o 'z2a' must be a Zope 2 interface attribute.
"""
if not isinstance(z2a, Z2_Attribute):
raise ValueError, 'Not a Zope 2 interface attribute!'
return Z3_Attribute(z2a.getName(), z2a.getDoc())
##############################################################################
#
# Copyright (c) 2004 Five Contributors. All rights reserved.
#
# This software is distributed under the terms of the Zope Public
# License (ZPL) v2.1. See COPYING.txt for more information.
#
##############################################################################
"""Provide basic browser functionality
$Id: browser.py 9730 2005-03-10 22:50:43Z jw $
"""
# python
import sys
from datetime import datetime
# Zope 2
import Acquisition
from Acquisition import aq_inner, aq_parent, aq_base
from AccessControl import ClassSecurityInfo
from Globals import InitializeClass
# Zope 3
from interfaces import ITraversable
from zope.interface import implements
from zope.interface.common.mapping import IItemMapping
from zope.component import getView
from zope.component import getViewProviding
from zope.app.traversing.browser.interfaces import IAbsoluteURL
from zope.app.location.interfaces import ILocation
from zope.app.location import LocationProxy
from zope.app.form.utility import setUpEditWidgets, applyWidgetsChanges
from zope.app.form.browser.submit import Update
from zope.app.form.interfaces import WidgetsError, MissingInputError
from zope.event import notify
from zope.app.form.utility import setUpWidgets, getWidgetsData
from zope.app.form.interfaces import IInputWidget, WidgetsError
from zope.schema.interfaces import ValidationError
from zope.app.event.objectevent import ObjectCreatedEvent, ObjectModifiedEvent
# Five
from Products.Five.pagetemplatefile import FivePageTemplateFile
class BrowserView(Acquisition.Explicit):
security = ClassSecurityInfo()
def __init__(self, context, request):
self.context = context
self.request = request
# XXX do not create any methods on the subclass called index_html,
# as this makes Zope 2 traverse into that first!
InitializeClass(BrowserView)
class AbsoluteURL(BrowserView):
"""An adapter for Zope3-style absolute_url using Zope2 methods
(original: zope.app.traversing.browser.absoluteurl)
"""
def __init__(self, context, request):
self.context, self.request = context, request
implements(IAbsoluteURL)
def __str__(self):
context = aq_inner(self.context)
return context.absolute_url()
__call__ = __str__
def breadcrumbs(self):
context = self.context.aq_inner
container = context.aq_parent
request = self.request
name = context.getId()
if container is None or self._isVirtualHostRoot() \
or not ITraversable.providedBy(container):
return (
{'name': name, 'url': context.absolute_url()},)
view = getViewProviding(container, IAbsoluteURL, request)
base = tuple(view.breadcrumbs())
base += (
{'name': name, 'url': ("%s/%s" % (base[-1]['url'], name))},)
return base
def _isVirtualHostRoot(self):
virtualrootpath = self.request.get('VirtualRootPhysicalPath', None)
if virtualrootpath is None:
return False
context = self.context.aq_inner
return context.restrictedTraverse(virtualrootpath) == context
class SiteAbsoluteURL(AbsoluteURL):
"""An adapter for Zope3-style absolute_url using Zope2 methods
This one is just used to stop breadcrumbs from crumbing up
to the Zope root.
(original: zope.app.traversing.browser.absoluteurl)
"""
def breadcrumbs(self):
context = self.context
request = self.request
return ({'name': context.getId(),
'url': context.absolute_url()
},)
class Macros:
implements(IItemMapping)
macro_pages = ()
aliases = {
'view': 'page',
'dialog': 'page',
'addingdialog': 'page'
}
def __getitem__(self, key):
key = self.aliases.get(key, key)
context = self.context
request = self.request
for name in self.macro_pages:
page = getView(context, name, request)
try:
v = page[key]
except KeyError:
pass
else:
return v
raise KeyError, key
class StandardMacros(BrowserView, Macros):
pass
class EditView(BrowserView):
"""Simple edit-view base class
Subclasses should provide a schema attribute defining the schema
to be edited.
"""
errors = ()
update_status = None
label = ''
# Fall-back field names computes from schema
fieldNames = property(lambda self: getFieldNamesInOrder(self.schema))
# Fall-back template
generated_form = FivePageTemplateFile('edit.pt')
def __init__(self, context, request):
BrowserView.__init__(self, context, request)
self._setUpWidgets()
def _setUpWidgets(self):
adapted = self.schema(self.context)
if adapted is not self.context:
if not ILocation.providedBy(adapted):
adapted = LocationProxy(adapted)
adapted.__parent__ = self.context
self.adapted = adapted
setUpEditWidgets(self, self.schema, source=self.adapted,
names=self.fieldNames)
def setPrefix(self, prefix):
for widget in self.widgets():
widget.setPrefix(prefix)
def widgets(self):
return [getattr(self, name+'_widget')
for name in self.fieldNames]
def changed(self):
# This method is overridden to execute logic *after* changes
# have been made.
pass
def update(self):
if self.update_status is not None:
# We've been called before. Just return the status we previously
# computed.
return self.update_status
status = ''
content = self.adapted
if Update in self.request.form.keys():
changed = False
try:
changed = applyWidgetsChanges(self, self.schema,
target=content, names=self.fieldNames)
# We should not generate events when an adapter is used.
# That's the adapter's job.
if changed and self.context is self.adapted:
notify(ObjectModifiedEvent(content))
except WidgetsError, errors:
self.errors = errors
status = "An error occured."
get_transaction().abort()
else:
setUpEditWidgets(self, self.schema, source=self.adapted,
ignoreStickyValues=True,
names=self.fieldNames)
if changed:
self.changed()
# XXX: Needs i18n support:
# formatter = self.request.locale.dates.getFormatter(
# 'dateTime', 'medium')
# status = _("Updated on ${date_time}")
# status.mapping = {'date_time': formatter.format(
# datetime.utcnow())}
status = "Updated on %s" % str(datetime.utcnow())
self.update_status = status
return status
class AddView(EditView):
"""Simple edit-view base class.
Subclasses should provide a schema attribute defining the schema
to be edited.
"""
def _setUpWidgets(self):
setUpWidgets(self, self.schema, IInputWidget, names=self.fieldNames)
def update(self):
if self.update_status is not None:
# We've been called before. Just return the previous result.
return self.update_status
if self.request.form.has_key(Update):
self.update_status = ''
try:
data = getWidgetsData(self, self.schema, names=self.fieldNames)
self.createAndAdd(data)
except WidgetsError, errors:
self.errors = errors
self.update_status = "An error occured."
return self.update_status
self.request.response.redirect(self.nextURL())
return self.update_status
def create(self, *args, **kw):
"""Do the actual instantiation."""
# hack to please typical Zope 2 factories, which expect id and title
args = ('tmp_id', 'Temporary title') + args
return self._factory(*args, **kw)
def createAndAdd(self, data):
"""Add the desired object using the data in the data argument.
The data argument is a dictionary with the data entered in the form.
"""
args = []
if self._arguments:
for name in self._arguments:
args.append(data[name])
kw = {}
if self._keyword_arguments:
for name in self._keyword_arguments:
if name in data:
kw[str(name)] = data[name]
content = self.create(*args, **kw)
adapted = self.schema(content)
errors = []
if self._set_before_add:
for name in self._set_before_add:
if name in data:
field = self.schema[name]
try:
field.set(adapted, data[name])
except ValidationError:
errors.append(sys.exc_info()[1])
if errors:
raise WidgetsError(*errors)
notify(ObjectCreatedEvent(content))
content = self.add(content)
adapted = self.schema(content)
if self._set_after_add:
for name in self._set_after_add:
if name in data:
field = self.schema[name]
try:
field.set(adapted, data[name])
except ValidationError:
errors.append(sys.exc_info()[1])
if errors:
raise WidgetsError(*errors)
return content
def add(self, content):
return self.context.add(content)
def nextURL(self):
return self.context.nextURL()
##############################################################################
#
# Copyright (c) 2004 Five Contributors. All rights reserved.
#
# This software is distributed under the terms of the Zope Public
# License (ZPL) v2.1. See COPYING.txt for more information.
#
##############################################################################
"""Browser directives
Directives to emulate the 'http://namespaces.zope.org/browser'
namespace in ZCML known from zope.app.
$Id: browserconfigure.py 9707 2005-03-08 15:14:24Z regebro $
"""
import os
from zope.interface import Interface
from zope.component import getGlobalService, ComponentLookupError
from zope.configuration.exceptions import ConfigurationError
from zope.component.servicenames import Presentation
from zope.publisher.interfaces.browser import IBrowserRequest
from zope.app.publisher.browser.viewmeta import pages as zope_app_pages
from zope.app.publisher.browser.viewmeta import view as zope_app_view
from zope.app.publisher.browser.globalbrowsermenuservice import\
menuItemDirective
from zope.app.component.metaconfigure import handler
from zope.app.component.interface import provideInterface
from zope.app.form.browser.metaconfigure import BaseFormDirective
from zope.app.container.interfaces import IAdding
from resource import FileResourceFactory, ImageResourceFactory
from resource import PageTemplateResourceFactory
from resource import DirectoryResourceFactory
from browser import BrowserView, EditView, AddView
from metaclass import makeClass
from security import getSecurityInfo, protectClass, protectName,\
initializeClass
from pagetemplatefile import ZopeTwoPageTemplateFile
def page(_context, name, permission, for_,
layer='default', template=None, class_=None,
allowed_interface=None, allowed_attributes=None,
attribute='__call__', menu=None, title=None,
):
try:
s = getGlobalService(Presentation)
except ComponentLookupError, err:
pass
_handle_menu(_context, menu, title, [for_], name, permission)
if not (class_ or template):
raise ConfigurationError("Must specify a class or template")
if allowed_attributes is None:
allowed_attributes = []
if allowed_interface is not None:
for interface in allowed_interface:
attrs = [n for n, d in interface.namesAndDescriptions(1)]
allowed_attributes.extend(attrs)
if attribute != '__call__':
if template:
raise ConfigurationError(
"Attribute and template cannot be used together.")
if not class_:
raise ConfigurationError(
"A class must be provided if attribute is used")
if template:
template = os.path.abspath(str(_context.path(template)))
if not os.path.isfile(template):
raise ConfigurationError("No such file", template)
if class_:
# new-style classes do not work with Five. As we want to import
# packages from z3 directly, we ignore new-style classes for now.
if type(class_) == type:
return
if attribute != '__call__':
if not hasattr(class_, attribute):
raise ConfigurationError(
"The provided class doesn't have the specified attribute "
)
cdict = getSecurityInfo(class_)
if template:
new_class = makeClassForTemplate(template, bases=(class_, ),
cdict=cdict)
elif attribute != "__call__":
# we're supposed to make a page for an attribute (read:
# method) and it's not __call__. We thus need to create a
# new class using our mixin for attributes.
cdict.update({'__page_attribute__': attribute})
new_class = makeClass(class_.__name__,
(class_, ViewMixinForAttributes),
cdict)
# in case the attribute does not provide a docstring,
# ZPublisher refuses to publish it. So, as a workaround,
# we provide a stub docstring
func = getattr(new_class, attribute)
if not func.__doc__:
# cannot test for MethodType/UnboundMethod here
# because of ExtensionClass
if hasattr(func, 'im_func'):
# you can only set a docstring on functions, not
# on method objects
func = func.im_func
func.__doc__ = "Stub docstring to make ZPublisher work"
else:
# we could use the class verbatim here, but we'll execute
# some security declarations on it so we really shouldn't
# modify the original. So, instead we make a new class
# with just one base class -- the original
new_class = makeClass(class_.__name__, (class_,), cdict)
else:
# template
new_class = makeClassForTemplate(template)
_handle_for(_context, for_)
_context.action(
discriminator = ('view', for_, name, IBrowserRequest, layer),
callable = handler,
args = (Presentation, 'provideAdapter',
IBrowserRequest, new_class, name, [for_], Interface, layer,
_context.info),
)
_context.action(
discriminator = ('five:protectClass', new_class),
callable = protectClass,
args = (new_class, permission)
)
if allowed_attributes:
for attr in allowed_attributes:
_context.action(
discriminator = ('five:protectName', new_class, attr),
callable = protectName,
args = (new_class, attr, permission)
)
_context.action(
discriminator = ('five:initialize:class', new_class),
callable = initializeClass,
args = (new_class,)
)
class pages(zope_app_pages):
def page(self, _context, name, attribute='__call__', template=None,
menu=None, title=None):
return page(_context,
name=name,
attribute=attribute,
template=template,
menu=menu, title=title,
**(self.opts))
def defaultView(_context, name, for_=None):
type = IBrowserRequest
_context.action(
discriminator = ('defaultViewName', for_, type, name),
callable = handler,
args = (Presentation,
'setDefaultViewName', for_, type, name),
)
_handle_for(_context, for_)
# view (named view with pages)
class view(zope_app_view):
def __call__(self):
(_context, name, for_, permission, layer, class_,
allowed_interface, allowed_attributes) = self.args
required = {}
cdict = {}
pages = {}
for pname, attribute, template in self.pages:
try:
s = getGlobalService(Presentation)
except ComponentLookupError, err:
pass
if template:
cdict[pname] = ZopeTwoPageTemplateFile(template)
if attribute and attribute != name:
cdict[attribute] = cdict[pname]
else:
if not hasattr(class_, attribute):
raise ConfigurationError("Undefined attribute",
attribute)
attribute = attribute or pname
required[pname] = permission
pages[pname] = attribute
# This should go away, but noone seems to remember what to do. :-(
if hasattr(class_, 'publishTraverse'):
def publishTraverse(self, request, name,
pages=pages, getattr=getattr):
if name in pages:
return getattr(self, pages[name])
view = zapi.queryView(self, name, request)
if view is not None:
return view
m = class_.publishTraverse.__get__(self)
return m(request, name)
else:
def publishTraverse(self, request, name,
pages=pages, getattr=getattr):
if name in pages:
return getattr(self, pages[name])
view = zapi.queryView(self, name, request)
if view is not None:
return view
raise NotFoundError(self, name, request)
cdict['publishTraverse'] = publishTraverse
if not hasattr(class_, 'browserDefault'):
if self.default or self.pages:
default = self.default or self.pages[0][0]
cdict['browserDefault'] = (
lambda self, request, default=default:
(self, (default, ))
)
elif providesCallable(class_):
cdict['browserDefault'] = (
lambda self, request: (self, ())
)
if class_ is not None:
bases = (class_, ViewMixinForTemplates)
else:
bases = (ViewMixinForTemplates)
try:
cname = str(name)
except:
cname = "GeneratedClass"
newclass = makeClass(cname, bases, cdict)
_handle_for(_context, for_)
if self.provides is not None:
_context.action(
discriminator = None,
callable = provideInterface,
args = ('', self.provides)
)
_context.action(
discriminator = ('view', for_, name, IBrowserRequest, layer,
self.provides),
callable = handler,
args = (Presentation, 'provideAdapter',
IBrowserRequest, newclass, name, [for_], self.provides,
layer, _context.info),
)
def _handle_for(_context, for_):
if for_ is not None:
_context.action(
discriminator = None,
callable = provideInterface,
args = ('', for_)
)
def _handle_menu(_context, menu, title, for_, name, permission):
if menu or title:
if not (menu and title):
raise ConfigurationError(
"If either menu or title are specified, they must "
"both be specified.")
if len(for_) != 1:
raise ConfigurationError(
"Menus can be specified only for single-view, not for "
"multi-views.")
return menuItemDirective(
_context, menu, for_[0], '@@' + str(name), title,
permission=permission)
return []
_factory_map = {'image':{'prefix':'ImageResource',
'count':0,
'factory':ImageResourceFactory},
'file':{'prefix':'FileResource',
'count':0,
'factory':FileResourceFactory},
'template':{'prefix':'PageTemplateResource',
'count':0,
'factory':PageTemplateResourceFactory}
}
def resource(_context, name, layer='default', permission='zope.Public',
file=None, image=None, template=None):
if ((file and image) or (file and template) or
(image and template) or not (file or image or template)):
raise ConfigurationError(
"Must use exactly one of file or image or template"
"attributes for resource directives"
)
res = file or image or template
res_type = ((file and 'file') or
(image and 'image') or
(template and 'template'))
factory_info = _factory_map.get(res_type)
factory_info['count'] += 1
res_factory = factory_info['factory']
class_name = '%s%s' % (factory_info['prefix'], factory_info['count'])
new_class = makeClass(class_name, (res_factory.resource,), {})
factory = res_factory(name, res, resource_factory=new_class)
_context.action(
discriminator = ('resource', name, IBrowserRequest, layer),
callable = handler,
args = (Presentation, 'provideResource',
name, IBrowserRequest, factory, layer),
)
_context.action(
discriminator = ('five:protectClass', new_class),
callable = protectClass,
args = (new_class, permission)
)
_context.action(
discriminator = ('five:initialize:class', new_class),
callable = initializeClass,
args = (new_class,)
)
_rd_map = {ImageResourceFactory:{'prefix':'DirContainedImageResource',
'count':0},
FileResourceFactory:{'prefix':'DirContainedFileResource',
'count':0},
PageTemplateResourceFactory:{'prefix':'DirContainedPTResource',
'count':0},
DirectoryResourceFactory:{'prefix':'DirectoryResource',
'count':0}
}
def resourceDirectory(_context, name, directory, layer='default',
permission='zope.Public'):
if not os.path.isdir(directory):
raise ConfigurationError(
"Directory %s does not exist" % directory
)
resource = DirectoryResourceFactory.resource
f_cache = {}
resource_factories = dict(resource.resource_factories)
resource_factories['default'] = resource.default_factory
for ext, factory in resource_factories.items():
if f_cache.get(factory) is not None:
continue
factory_info = _rd_map.get(factory)
factory_info['count'] += 1
class_name = '%s%s' % (factory_info['prefix'], factory_info['count'])
factory_name = '%s%s' % (factory.__name__, factory_info['count'])
f_resource = makeClass(class_name, (factory.resource,), {})
f_cache[factory] = makeClass(factory_name, (factory,),
{'resource':f_resource})
for ext, factory in resource_factories.items():
resource_factories[ext] = f_cache[factory]
default_factory = resource_factories['default']
del resource_factories['default']
cdict = {'resource_factories':resource_factories,
'default_factory':default_factory}
factory_info = _rd_map.get(DirectoryResourceFactory)
factory_info['count'] += 1
class_name = '%s%s' % (factory_info['prefix'], factory_info['count'])
dir_factory = makeClass(class_name, (resource,), cdict)
factory = DirectoryResourceFactory(name, directory,
resource_factory=dir_factory)
new_classes = [dir_factory,
] + [f.resource for f in f_cache.values()]
_context.action(
discriminator = ('resource', name, IBrowserRequest, layer),
callable = handler,
args = (Presentation, 'provideResource',
name, IBrowserRequest, factory, layer),
)
for new_class in new_classes:
_context.action(
discriminator = ('five:protectClass', new_class),
callable = protectClass,
args = (new_class, permission)
)
_context.action(
discriminator = ('five:initialize:class', new_class),
callable = initializeClass,
args = (new_class,)
)
#
# Form generation from schema
#
def EditViewFactory(name, schema, label, permission, layer,
template, default_template, bases, for_, fields,
fulledit_path=None, fulledit_label=None, menu=u''):
s = getGlobalService(Presentation)
class_ = makeClassForTemplate(template, used_for=schema, bases=bases)
class_.schema = schema
class_.label = label
class_.fieldNames = fields
class_.fulledit_path = fulledit_path
if fulledit_path and (fulledit_label is None):
fulledit_label = "Full edit"
class_.fulledit_label = fulledit_label
class_.generated_form = ZopeTwoPageTemplateFile(default_template)
# Not the prettiest solution, but it works...
class_.__init__ = EditView.__init__
s.provideView(for_, name, IBrowserRequest, class_, layer)
class EditFormDirective(BaseFormDirective):
view = EditView
default_template = 'edit.pt'
title = 'Edit'
def _handle_menu(self):
if self.menu:
menuItemDirective(
self._context, self.menu, self.for_ or self.schema,
'@@' + self.name, self.title, permission=self.permission)
def __call__(self):
self._processWidgets()
self._handle_menu()
self._context.action(
discriminator=self._discriminator(),
callable=EditViewFactory,
args=self._args(),
kw={'menu': self.menu},
)
def AddViewFactory(name, schema, label, permission, layer,
template, default_template, bases, for_,
fields, content_factory, arguments,
keyword_arguments, set_before_add, set_after_add,
menu=u''):
s = getGlobalService(Presentation)
class_ = makeClassForTemplate(template, used_for=schema, bases=bases)
class_.schema = schema
class_.label = label
class_.fieldNames = fields
class_._factory = content_factory
class_._arguments = arguments
class_._keyword_arguments = keyword_arguments
class_._set_before_add = set_before_add
class_._set_after_add = set_after_add
class_.generated_form = ZopeTwoPageTemplateFile(default_template)
s.provideView(for_, name, IBrowserRequest, class_, layer)
class AddFormDirective(BaseFormDirective):
view = AddView
default_template = 'add.pt'
for_ = IAdding
# default add form information
description = None
content_factory = None
arguments = None
keyword_arguments = None
set_before_add = None
set_after_add = None
def _handle_menu(self):
if self.menu or self.title:
if (not self.menu) or (not self.title):
raise ValueError("If either menu or title are specified, "
"they must both be specified")
# Add forms are really for IAdding components, so do not use
# for=self.schema.
menuItemDirective(
self._context, self.menu, self.for_, '@@' + self.name,
self.title, permission=self.permission,
description=self.description)
def _handle_arguments(self, leftover=None):
schema = self.schema
fields = self.fields
arguments = self.arguments
keyword_arguments = self.keyword_arguments
set_before_add = self.set_before_add
set_after_add = self.set_after_add
if leftover is None:
leftover = fields
if arguments:
missing = [n for n in arguments if n not in fields]
if missing:
raise ValueError("Some arguments are not included in the form",
missing)
optional = [n for n in arguments if not schema[n].required]
if optional:
raise ValueError("Some arguments are optional, use"
" keyword_arguments for them",
optional)
leftover = [n for n in leftover if n not in arguments]
if keyword_arguments:
missing = [n for n in keyword_arguments if n not in fields]
if missing:
raise ValueError(
"Some keyword_arguments are not included in the form",
missing)
leftover = [n for n in leftover if n not in keyword_arguments]
if set_before_add:
missing = [n for n in set_before_add if n not in fields]
if missing:
raise ValueError(
"Some set_before_add are not included in the form",
missing)
leftover = [n for n in leftover if n not in set_before_add]
if set_after_add:
missing = [n for n in set_after_add if n not in fields]
if missing:
raise ValueError(
"Some set_after_add are not included in the form",
missing)
leftover = [n for n in leftover if n not in set_after_add]
self.set_after_add += leftover
else:
self.set_after_add = leftover
def __call__(self):
self._processWidgets()
self._handle_menu()
self._handle_arguments()
self._context.action(
discriminator=self._discriminator(),
callable=AddViewFactory,
args=self._args()+(self.content_factory, self.arguments,
self.keyword_arguments,
self.set_before_add, self.set_after_add),
kw={'menu': self.menu},
)
#
# mixin classes / class factories
#
class ViewMixinForAttributes(BrowserView):
# we have an attribute that we can simply tell ZPublisher to go to
def __browser_default__(self, request):
return self, (self.__page_attribute__,)
# this is technically not needed because ZPublisher finds our
# attribute through __browser_default__; but we also want to be
# able to call pages from python modules, PythonScripts or ZPT
def __call__(self, *args, **kw):
attr = self.__page_attribute__
meth = getattr(self, attr)
return meth(*args, **kw)
class ViewMixinForTemplates(BrowserView):
# short cut to get to macros more easily
def __getitem__(self, name):
if name == 'macros':
return self.index.macros
return self.index.macros[name]
# make the template publishable
def __call__(self, *args, **kw):
return self.index(self, *args, **kw)
def makeClassForTemplate(src, template=None, used_for=None,
bases=(), cdict=None):
# XXX needs to deal with security from the bases?
if cdict is None:
cdict = {}
cdict.update({'index': ZopeTwoPageTemplateFile(src, template)})
bases += (ViewMixinForTemplates,)
class_ = makeClass("SimpleViewClass from %s" % src, bases, cdict)
if used_for is not None:
class_.__used_for__ = used_for
return class_
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser"
xmlns:five="http://namespaces.zope.org/five">
<include file="meta.zcml" />
<include file="services.zcml" />
<include file="interfaces.zcml" />
<include file="permissions.zcml" />
<include package="zope.app.traversing" />
<include package="zope.app.form.browser" />
<!-- do 'traditional' traversing by default; needed by ZPT -->
<adapter
for="*"
factory=".traversable.FiveTraversable"
provides="zope.app.traversing.interfaces.ITraversable"
/>
<adapter
for="*"
factory="zope.app.traversing.adapters.Traverser"
provides="zope.app.traversing.interfaces.ITraverser"
/>
<adapter
for="*"
factory=".viewable.BrowserDefault"
provides=".interfaces.IBrowserDefault"
/>
<browser:page
for="*"
name="absolute_url"
class=".browser.AbsoluteURL"
permission="zope.Public"
allowed_interface="zope.app.traversing.browser.interfaces.IAbsoluteURL"
/>
<browser:page
for="*"
template="five_template.pt"
name="five_template"
permission="zope.Public"
/>
<view
for="*"
factory=".browser.AbsoluteURL"
type="zope.publisher.interfaces.http.IHTTPRequest"
permission="zope.Public"
provides="zope.app.traversing.browser.interfaces.IAbsoluteURL"
/>
<browser:page
for="zope.app.traversing.interfaces.IContainmentRoot"
name="absolute_url"
class=".browser.SiteAbsoluteURL"
permission="zope.Public"
allowed_interface="zope.app.traversing.browser.interfaces.IAbsoluteURL"
/>
<view
for="zope.app.traversing.interfaces.IContainmentRoot"
factory=".browser.SiteAbsoluteURL"
type="zope.publisher.interfaces.http.IHTTPRequest"
permission="zope.Public"
provides="zope.app.traversing.browser.interfaces.IAbsoluteURL"
/>
<browser:view
for=".interfaces.IObjectManager"
name="+"
class=".adding.ContentAdding"
permission="zope2.ViewManagementScreens"
>
<browser:page name="index.html" template="adding.pt" />
<browser:page name="action.html" attribute="action" />
</browser:view>
<adapter
for=".interfaces.IObjectManager"
factory=".adding.ObjectManagerNameChooser"
provides="zope.app.container.interfaces.INameChooser"
/>
<!-- this is really lying, but it's to please checkContainer -->
<five:implements class="OFS.ObjectManager.ObjectManager"
interface="zope.app.container.interfaces.IContainer" />
<!-- make Zope 2's REQUEST implement the right thing -->
<five:implements class="ZPublisher.HTTPRequest.HTTPRequest"
interface="zope.publisher.interfaces.browser.IBrowserRequest"
/>
</configure>
Zope Public License (ZPL) Version 2.1
-------------------------------------
A copyright notice accompanies this license document that
identifies the copyright holders.
This license has been certified as open source. It has also
been designated as GPL compatible by the Free Software
Foundation (FSF).
Redistribution and use in source and binary forms, with or
without modification, are permitted provided that the
following conditions are met:
1. Redistributions in source code must retain the
accompanying copyright notice, this list of conditions,
and the following disclaimer.
2. Redistributions in binary form must reproduce the accompanying
copyright notice, this list of conditions, and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
3. Names of the copyright holders must not be used to
endorse or promote products derived from this software
without prior written permission from the copyright
holders.
4. The right to distribute this software or to use it for
any purpose does not give you the right to use
Servicemarks (sm) or Trademarks (tm) of the copyright
holders. Use of them is covered by separate agreement
with the copyright holders.
5. If any files are modified, you must cause the modified
files to carry prominent notices stating that you changed
the files and the date of any change.
Disclaimer
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS''
AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
NO EVENT SHALL THE COPYRIGHT HOLDERS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
=================================
ZCML Directives supported by Five
=================================
Five tries to use the Zope 3 ZCML directives where possible, though
does sometimes subset the possible attributes. It also introduces a
few directives of its own under the ``five`` namespace.
Directives are listed per namespace, in alphabetic order.
zope ``http://namespaces.zope.org/zope``
========================================
adapter
-------
Hook an adapter factory to an interface.
content
-------
Declare interface and permissions on content object. Declares Zope 2
permissions.
permission
----------
Way to make Zope 2 permissions available to Five, ``title`` is
permission name.
redefinePermission
------------------
Redefine a permission in included ZCML as another one.
service
-------
Declare a global service
serviceType
-----------
Declare a type of service.
skin
----
Declare a skin, consisting of layers.
utility
-------
Declare a global utility.
browser ``http://namespaces.zope.org/browser``
==============================================
page
----
Declare a page view for an interface. Permission is a Zope 2
permission.
pages
-----
Declare multiple page views for an interface. Permissions are Zope 2
permissions.
defaultView
-----------
Declare the name of the view that should be used for the default when viewing
the object; i.e. when the object is traversed to without a view.
defaultSkin
-----------
Declare the default skin used.
interface
---------
Define an interface in ZCML.
layer
-----
Declare a layer.
menu
----
Declare a menu
menuItem, menuItems
-------------------
Declare menuItems
five ``http://namespaces.zope.org/five``
========================================
implements
----------
Make a class declare it implements an interface.
loadProducts
------------
Loads ZCML in all Zope 2 products. First processes all ``meta.zcml``
files, then processes all ``configure.zcml`` files.
loadProductsOverrides
---------------------
Loads overriding ZCML in all products (``overrides.zcml``).
traversable
-----------
Make a Zope 2 content class traversable in the Zope 3 manner using
Five. This is used to attached views, resources and other things to
Zope 2 objects.
defaultViewable
---------------
Make a Zope 2 content class use a Zope 3 default view when looking at
it without any paths appended to it. This works then instead of
``index_html`` in Zope 2.
pagesFromDirectory
------------------
Load all *.pt files in a directory as pages. Useful when you want to
share templates between Five and CMF, so you can declare pages like
this is a similar way to setting up skin folders in portal_skins.
browser:editform
----------------
Create an edit form based on a schema.
browser:addform
---------------
Create an add form based on a schema.
=============
Five features
=============
Five features are mostly Zope 3 features, though Five has some extras,
and some limitations.
Zope 3 interfaces
=================
Everything in the ``zope.interface`` package should work. Zope 3
interfaces are the foundation of the component architecture, and also
the foundation of schemas.
ZCML
====
ZCML is the Zope Configuration Markup Language, an XML application.
Zope 3 (and Five) code consists of a lot of components that can be
plugged together using ZCML.
If you put a ``site.zcml`` in the home directory of your Zope
instance, this is the root of the ZCML tree. An example of
``site.zcml`` is in ``site.zcml.in``. If you don't place a
``site.zcml``, Five falls back on ``fallback.zcml``.
ZCML in Five has special directive, ``five:loadProducts``, to load the
ZCML (``meta.zcml``, ``configure.zcml``) of all installed Zope 2
products, if available.
Another special directive, ``five:loadProductsOverrides`` is available
to load any overriding ZCML (``overrides.zcml``) in these products. In
the ``overrides.zcml`` you can override existing views or adapters, in
this or in other products.
Adapters
========
You can use adapters in Five, just like in Zope 3.
Zope 3 views
============
Zope 3 views work in Five, including layers and skins. To make them
work however, you need to make a Zope 2 class "traversable". This can
be done by using the ``five:traversable`` directive in ZCML.
Page templates
==============
Five before release 0.3 used to use Zope 3's page template engine, but
in the interests of increased compatibility with Zope 2, we've
switched to using Zope 2's. There should be no real difference to any
code, however. We may decide to switch back to Zope 3's engine again
eventually if we can resolve the compatibility issues.
One thing to be aware of is that the page template engine runs
completely in trusted mode, just like Python code. That is, as soon as
the page template engine is running, no Zope 2 or Zope 3 security
checks are made.
Edit and add forms
==================
Five supports edit and add forms. Typical Zope 3 examples of these
should work.
Security declarations
=====================
Five aims to eradicate ``declareProtected``, ``ClassSecurityInfo`` and
``initializeClass`` from your Zope 2 code.
In order to do this, Five provides the Zope 3 way of declaring
permissions from ZCML, but uses the Zope 2 mechanisms to actually set
them. To declare permissions for methods and templates on views you
use the ``permission`` attribute on the ``browser:page`` directive,
and specify a Zope 2 permission (given a Zope 3 name). You can find a
list of these permissions in ``permissions.zcml`` in Five. The
permission check takes place before the view is executed.
The ``content`` directive can also be used to declare permissions on
Zope 2 content classes. Note however that these permissions will be
ignored by views anyway, as they are trusted -- it only serves to
protect directly exposed methods on content classes (the python
scripts and the ZPublisher).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%deffont "standard" xfont "helvetica-medium-r"
%deffont "thick" xfont "helvetica-bold-r"
%deffont "typewriter" xfont "courier-medium-r"
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Default settings per each line numbers.
%%
%default 1 area 90 90, leftfill, size 2, fore "gray20", back "white", font "standard", hgap 0
%default 2 size 7, vgap 10, prefix " ", ccolor "blue"
%default 3 size 2, bar "gray70", vgap 10
%default 4 size 5, fore "gray20", vgap 30, prefix " ", font "standard"
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Default settings that are applied to TAB-indented lines.
%%
%tab 1 size 5, vgap 40, prefix " ", icon box "red" 50
%tab 2 size 4, vgap 40, prefix " ", icon arc "yellow" 50
%tab 3 size 3, vgap 40, prefix " ", icon delta3 "white" 40
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%page
Five - Zope 3 in Zope 2
%center
Martijn Faassen, Infrae
faassen@infrae.com
%page
Motto
It was the dawn of the third age of Zope. The Five project was a dream given form. Its goal: to use Zope 3 technologies in Zope 2.7 by creating a Zope 2 product where Zope 3 and Zope 2 could work out their differences peacefully.
(Babylon 5 season 1 intro, creatively quoted)
%page
Motto 2
The Law of Fives states simply that: ALL THINGS HAPPEN IN FIVES, OR ARE DIVISIBLE BY OR ARE MULTIPLES OF FIVE, OR ARE SOMEHOW DIRECTLY OR INDIRECTLY RELATED TO FIVE.
THE LAW OF FIVES IS NEVER WRONG.
(Principia Discordia)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%page
The problem
We're using Zope 2 in production
Zope 2 is showing its age
Zope 3 has better ways to do things
But can't just switch, we have customers!
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%page
Benefits of using Zope 3 in Zope 2
Able to use Zope 3 technologies right away
Don't reinvent the wheel/APIs
Better prepared for Zope 3 transition
Evolution, not revolution
Convergence, not divergence
%page
What works now?
Interfaces (zope.interface)
Schema (zope.schema)
ZCML (zope.configuration)
Adapters (zope.component)
Views, including layers, skins (zope.component)
%page
Brief demo
Show ZCML, adapters and views in action
%page
Next?
Utilities (global ones should work)
Forms
Views (improve the current system)
Who knows?
%page
Plans
Relicense from BSD to generic ZPL 2.1
Move from CVS at Infrae into SVN at codespeak.net
Convergence; join us!
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%deffont "standard" xfont "helvetica-medium-r"
%deffont "thick" xfont "helvetica-bold-r"
%deffont "typewriter" xfont "courier-medium-r"
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Default settings per each line numbers.
%%
%default 1 area 90 90, leftfill, size 2, fore "gray20", back "white", font "standard", hgap 0
%default 2 size 7, vgap 10, prefix " ", ccolor "blue"
%default 3 size 2, bar "gray70", vgap 10
%default 4 size 5, fore "gray20", vgap 30, prefix " ", font "standard"
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Default settings that are applied to TAB-indented lines.
%%
%tab 1 size 5, vgap 40, prefix " ", icon box "red" 50
%tab 2 size 4, vgap 40, prefix " ", icon arc "yellow" 50
%tab 3 size 3, vgap 40, prefix " ", icon delta3 "white" 40
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%page
Five - Zope 3 in Zope 2
%center
Martijn Faassen, Infrae
faassen@infrae.com
Five developer
%page
Five future directions
What might happen
%page
Unique id service support
Foundation is there in form of events
Unfortunately implementation can not be the same
Objects are referenced differently in Zope 3
Local services/utilities are difficult in Zope 2
Could lead to Zope 3 catalog support
%page
Page template engine improvements
Right now we already use Zope 3 page templates
These are unicode-only (and work with plain ascii)
To support Zope 2 content, need classic non-ascii string support
Issue in Plone, not in Silva (heh heh)
%page
Zope 2.8 and ZODB 3.3
Should be able to work much better with new-style objects
Things like local services/utilities might be doable
%page
Integration with Plone, CMF, Silva, UnionCMS etc
Sharing a common base is good
Zope 3 is good
That base should be Zope 3
Five can help us start sharing today
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%deffont "standard" xfont "helvetica-medium-r"
%deffont "thick" xfont "helvetica-bold-r"
%deffont "typewriter" xfont "courier-medium-r"
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Default settings per each line numbers.
%%
%default 1 area 90 90, leftfill, size 2, fore "gray20", back "white", font "standard", hgap 0
%default 2 size 7, vgap 10, prefix " ", ccolor "blue"
%default 3 size 2, bar "gray70", vgap 10
%default 4 size 5, fore "gray20", vgap 30, prefix " ", font "standard"
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Default settings that are applied to TAB-indented lines.
%%
%tab 1 size 5, vgap 40, prefix " ", icon box "red" 50
%tab 2 size 4, vgap 40, prefix " ", icon arc "yellow" 50
%tab 3 size 3, vgap 40, prefix " ", icon delta3 "white" 40
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%page
Five - Zope 3 in Zope 2
%center
Martijn Faassen, Infrae
faassen@infrae.com
Five developer
%page
Interfaces, adapters
What are interfaces?
What are adapters?
Why?
A very quick introduction
%page
Actually
This tutorial applies to Zope 3 as much as to Five
Indication Five reached its goal in this area
%page
Interface example
%size 4, fore "blue"
from zope.interface import Interface
class IElephant(Interface):
"""An elephant is a big grey animal.
"""
def getAngerLevel():
"Return anger level on scale 0 (placid) to 10 (raging)"
def trample(target):
"Trample the target."
def trumpet():
"Make loud noise with trunk."
%page
Interface example, continued
%size 4, fore "blue"
from zope.interface import implements
class AfricanElephant:
implements(IElephant)
def getAngerLevel(self):
return 5 # always pretty stroppy
def trample(self, target):
target.flatten()
def trumpet(self):
return "A terrible racket"
%page
Interfaces
Interfaces are about the what, not the how
Interfaces don't do anything, they just describe
Code can state what interfaces objects provide
Code can introspect whether objects provide interfaces
%page
Why interfaces?
They are documentation
Make multiple implementations of same interface easier
Allows you to program against published APIs
Allow glueing by interface
%page
Component architecture
zope.component part of Zope 3
allows glueing together of components in various ways
a component is an object which provides an interface
a Zope 2 object with a Zope 3 interface is a component
%page
Adapters, example
%size 4, fore "blue"
class INoiseMaker(Interface):
"""Something that makes noise.
"""
def makeNoise():
"Returns the noise that's made."
%page
Adapters, example continued
%size 4, fore "blue"
class ElephantNoiseMaker:
"""Adapts elephant to noise maker.
"""
implements(INoiseMaker)
def __init__(self, context):
self.context = context
def makeNoise(self):
return self.context.trumpet()
%page
Adapters, example continued 2
%size 4, fore "blue"
>>> elephant = AfricanElephant()
>>> noise_maker = ElephantNoiseMaker(elephant)
>>> print noise_maker.makeNoise()
'A terrible racket'
%page
Adapters
Add behavior to object without changing its class
More manageable than mixins
Define new behavior in terms of other behavior
%page
Adapters, continued
Less framework burden on adapted objects
They only need to be a component
Adapted doesn't know about the adapter
Adapter is a component itself
%page
Adapter lookup
We just manually glued the adapter to the adapted
What if we had INoiseMaker adapters for other objects?
We want a universal way to say: give me a INoiseMaker for this object
This allows use to write more generic code
%page
Adapter lookup, example
%size 4, fore "blue"
for animal in animal_farm:
noise_maker = INoiseMaker(animal)
print noise_maker.makeNoise()
%page
Adapter glueing
System need to be informed what can adapt what
Zope Configuration Markup Language (ZCML) is used for that
%page
ZCML example
%size 4, fore "blue"
<configure xmlns="http://namespaces.zope.org/zope">
<adapter
for=".module.IElephant"
provides=".module.INoiseMaker"
factory=".module.ElephantNoiseMaker" />
<adapter
for=".other.IChicken"
provides=".module.INoiseMaker"
factory=".other.ChickenNoiseMaker" />
</configure>
%page
ZCML, what we just said
The adapter ElephantNoiseMaker adapts any object that provides IElephant to a INoiseMaker
The adapter ChickenNoiseMaker adapts any object that provides IChicken to a INoiseMaker
%page
This works in Zope 2 with Five
This works in Zope 2 with Five
Your objects just need to be components (provide Zope 3 interfaces)
Your ZCML goes into configure.zcml in your product
That's it
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%deffont "standard" xfont "helvetica-medium-r"
%deffont "thick" xfont "helvetica-bold-r"
%deffont "typewriter" xfont "courier-medium-r"
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Default settings per each line numbers.
%%
%default 1 area 90 90, leftfill, size 2, fore "gray20", back "white", font "standard", hgap 0
%default 2 size 7, vgap 10, prefix " ", ccolor "blue"
%default 3 size 2, bar "gray70", vgap 10
%default 4 size 5, fore "gray20", vgap 30, prefix " ", font "standard"
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Default settings that are applied to TAB-indented lines.
%%
%tab 1 size 5, vgap 40, prefix " ", icon box "red" 50
%tab 2 size 4, vgap 40, prefix " ", icon arc "yellow" 50
%tab 3 size 3, vgap 40, prefix " ", icon delta3 "white" 40
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%page
Five - Zope 3 in Zope 2
%center
Martijn Faassen, Infrae
faassen@infrae.com
Five developer
%page
An Introduction to Five
Why Five?
What is Five?
Where are we, where are we going?
%page
Motto
It was the dawn of the third age of Zope. The Five project was a dream given form. Its goal: to use Zope 3 technologies in Zope 2.7 by creating a Zope 2 product where Zope 3 and Zope 2 could work out their differences peacefully.
(Babylon 5 season 1 intro, creatively quoted)
%page
Motto 2
The Law of Fives states simply that: ALL THINGS HAPPEN IN FIVES, OR ARE DIVISIBLE BY OR ARE MULTIPLES OF FIVE, OR ARE SOMEHOW DIRECTLY OR INDIRECTLY RELATED TO FIVE.
THE LAW OF FIVES IS NEVER WRONG.
(Principia Discordia)
%page
The problem
We're using Zope 2 in production
Zope 2 is showing its age
Zope 3 has better ways to do things
But can't just switch, we have codebases, customers!
%page
Benefits of using Zope 3 in Zope 2
Able to use Zope 3 technologies right away
Don't reinvent the wheel/APIs
Better prepared for Zope 3 transition
Evolution, not revolution
Convergence, not divergence (this is important)
%page
Divergence
Infrae created Silva, Nuxeo CPS, etc
Everybody else started using Plone (why?!)
I want to use cool Plone technology
Silva is cool too, you may want to use it
I don't want to have to reinvent every wheel (just some)
%page
What's stopping us from sharing?
Zope 2 components are hard to share between apps
Even CMF components need work to share
Especially if you don't use CMF... (Silva)
Zope 2 framework burden is making it hard
Clean Python code is easier to share
%page
Convergence
Unify our diverse efforts
Zope 3 allows you to write Python, less framework sacrifices
Zope 3 allows the glueing of components
Zope 3 is the future
Five makes some of the future available today
%page
What works now? - an overview
Interfaces
Schema
ZCML
Adapters
Views, including layers, skins
%page
What works now, continued
Zope 3 page template engine
Traversal, resources
Zope 2 security from ZCML
Events
Beginnings of forms machinery
%page
Progress made since June
Initial announcement at Europython
As promised, moved to SVN at codespeak.net
Got website, mailing list
People joined the project
%page
Progress made since June, continued
Lots of excellent contributions!
Much better view infrastructure (traversal)
ZCML's interaction with Zope 2 products much improved
UnionCMS and other projects are starting to use it!
%page
The Zope 3 Base
Five is part of the Zope 3 Base
Zope 3 Base - All Your Bobobase Are Belong To Us
Possibly the cutest Zope 3 website anywhere
http://codespeak.net/z3
%page
Zope 3 Base
%center
%image "z3-banner.png"
%page
Zope 3 Base, continued
Second area of Zope 3 related development
Equivalent of Plone collective, for Zope 3
More freewheeling than dev.zope.org
Less freewheeling than Plone collective, however
Cuter than both
%page
Evolution: Five-ification
Five is not just for new Zope 2 projects
Five can interoperate with existing Zope 2 applications
Five in Plone - Flon
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%deffont "standard" xfont "helvetica-medium-r"
%deffont "thick" xfont "helvetica-bold-r"
%deffont "typewriter" xfont "courier-medium-r"
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Default settings per each line numbers.
%%
%default 1 area 90 90, leftfill, size 2, fore "gray20", back "white", font "standard", hgap 0
%default 2 size 7, vgap 10, prefix " ", ccolor "blue"
%default 3 size 2, bar "gray70", vgap 10
%default 4 size 5, fore "gray20", vgap 30, prefix " ", font "standard"
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Default settings that are applied to TAB-indented lines.
%%
%tab 1 size 5, vgap 40, prefix " ", icon box "red" 50
%tab 2 size 4, vgap 40, prefix " ", icon arc "yellow" 50
%tab 3 size 3, vgap 40, prefix " ", icon delta3 "white" 40
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%page
Five - Zope 3 in Zope 2
%center
Martijn Faassen, Infrae
faassen@infrae.com
Five developer
%page
Five Misc Topics
A number of as-yet uncategorized Five-related topics
%page
Resources
Various kinds of resources available
File, image, page template resource
Accessible through ++resource++ namespace
%page
Resources, example
%size 4, fore "blue"
<browser:resource
image="z3base.png"
name="z3base.png"
permission="zope2.ViewManagementScreens"
/>
%page
Resources
Any Five traversable object now has can be used to get to resource
url: path/to/object/++resource++z3base.png
Jim says this is not exactly Zope 3 as it ruins caching
%page
ZCML
ZCML can optionally be put in etc/site.zcml
If not, Five will automatically use included zcml
This zcml is in skel
%page
ZCML continued
ZCML loads any configure.zcml in all products
This is driven by five:loadProducts in site.zcml
Overrides are possible in override.zcml
This is driven by five:loadProductsOverrides in site.zcml
%page
Bridging interfaces
"Bride of Frankenzope"
Utility functions in bridge.py
Can convert Zope 2 interface to Zope 3 interface
%page
Events
Can instruct Zope 2 object to send Zope 3 style events using five:sendEvents
These events sent upon copy/move/rename in Zope 2
IObjectMovedEvent, IObjectAddedEvent, IObjectCopiedEvent, IObjectRemovedEvent
Can set up functions to subscribe to these events
%page
Content directive and permissions
Use content directive to declare Zope 2 permissions Zope 3 style
Declare permissions from ZCML, no more declareProtected()
Your classes look cleaner as a result
%page
Macros
Zope 3 way to aggregate macros into single object
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%deffont "standard" xfont "helvetica-medium-r"
%deffont "thick" xfont "helvetica-bold-r"
%deffont "typewriter" xfont "courier-medium-r"
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Default settings per each line numbers.
%%
%default 1 area 90 90, leftfill, size 2, fore "gray20", back "white", font "standard", hgap 0
%default 2 size 7, vgap 10, prefix " ", ccolor "blue"
%default 3 size 2, bar "gray70", vgap 10
%default 4 size 5, fore "gray20", vgap 30, prefix " ", font "standard"
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% Default settings that are applied to TAB-indented lines.
%%
%tab 1 size 5, vgap 40, prefix " ", icon box "red" 50
%tab 2 size 4, vgap 40, prefix " ", icon arc "yellow" 50
%tab 3 size 3, vgap 40, prefix " ", icon delta3 "white" 40
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%page
Five - Zope 3 in Zope 2
%center
Martijn Faassen, Infrae
faassen@infrae.com
Five developer
%page
Views with Five
What are views?
Why?
How to make them work?
%page
Actually
This tutorial contains only a few Five specific bits
Otherwise it applies to Zope 3 as much as to Five
The Five specific bits are mainly some extra ZCML directives
These are in their own ZCML namespace
%page
Page example: overview.pt
%size 4, fore "blue"
<html>
<body>
<p tal:content="context/objectIds"></p>
</body>
</html>
%page
Page example: configure.zcml
%size 4, fore "blue"
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser"
xmlns:five="http://namespaces.zope.org/five">
<five:traversable
class="OFS.Folder.Folder"
/>
<browser:page
for="Products.Five.interfaces.IFolder"
name="overview.html"
template="overview.pt"
permission="zope2.ViewManagementScreens"
/>
</configure>
%page
What works now
some/folder/overview.html
%page
Hooking up the page, explanation
Much like hooking up an adapter
Adapter provides new interface (API) for developer
View provides new interface (UI) for user
Only five-specific thing is making Folder Zope-3 traversable
Well, and the Zope 2 permission.
%page
Hooking up a page, with class
We need some helper methods
Very similar to the way you'd use Python scripts in Zope 2
%page
View class example: overview2.pt
%size 4, fore "blue"
<html>
<body>
<p tal:content="view/reversedIds"></p>
</body>
</html>
%page
View class example: browser.py
%size 4, fore "blue"
from Products.Five import BrowserView
class Overview(BrowserView):
def reversedIds(self):
result = []
for id in self.context.objectIds():
l = list(id)
l.reverse()
reversed_id = ''.join(l)
result.append(reversed_id)
return result
%page
Example: configure.zcml
%size 4, fore "blue"
<browser:page
for="Products.Five.interfaces.IFolder"
name="overview2.html"
template="overview2.pt"
permission="zope2.ViewManagementScreens"
class=".browser.Overview"
/>
%page
A note on security
There is none: both python code and ZPT are trusted
Only checks are happening on the outside
Performance benefit
Advantage of simplicity
%page
Publishing an attribute
Expose python method on view directly to the web
%page
Attribute example: browser.py
%size 4, fore "blue"
def directlyPublished(self):
return "This is directly published"
%page
Attribute example: configure zcml
%size 4, fore "blue"
<browser:page
for="Products.Five.interfaces.IFolder"
name="test.html"
class=".browser.Overview"
attribute="directlyPublished"
permission="zope2.ViewManagementScreens"
/>
%page
Publishing multiple pages
Convenience directive: browser:pages
%page
Multiple pages example
%size 4, fore "blue"
<browser:pages
for="Products.Five.interfaces.IFolder"
class=".browser.NewExample"
permission="zope2.ViewManagementScreens"
>
<browser:page
name="one.html"
template="one.pt"
/>
<browser:page
name="two.html"
attribute="two"
/>
</browser:pages>
%page
Default view for object
We can now set views that are named
What if we traverse to the object itself?
Use five:defaultViewable and browser:defaultView
%page
Uh oh
This doesn't seem to work yet with Zope folders. Sidnei, help!
So we'll try it with a custom SimpleItem-based object
%page
DefaultView example
%size 4, fore "blue"
<five:defaultViewable class=".democontent.DemoContent" />
<browser:defaultView
for=".democontent.IDemoContent"
name="someview.html" />
%page
Conclusions
This works much the same way as Zope 3 does too
Can supplement existing view systems in Zope 2
Five specific code is mostly isolated in five:traversable and five:defaultViewable
\ No newline at end of file
Five, the Zope 3 in Zope 2 project
==================================
What is Five?
-------------
Five is a Zope 2 product that allows you to integrate Zope 3
technologies into Zope 2, today. Five right now allows you to use the
following Zope 3 technologies in Zope 2:
* Zope 3 interfaces
* adapters
* pages (views), including skins and layers, and edit and add forms
* ZCML
It is possible to add Zope 3 style views to your own Zope 2 objects,
or to existing ones, even normal Folders!
Five works with a straight Zope 2.7 installation, as long as Zope 3
has been installed. See Five's INSTALL.txt for more information on how
to set it up.
We're in the process of evaluating lots more Zope 3 technologies for
integration into Zope 2. This is the right moment for interested Zope
2 and Zope 3 developers to jump in. We're looking for cooperation
between different Zope 2 projects so that this can be a foundational
system for us all.
Download
--------
2005-003-11 -- We have released Five 0.3! Download it here:
http://codespeak.net/z3/five/release/Five-0.3.tgz
And changes here:
http://codespeak.net/z3/five/CHANGES.html
2004-09-24 -- Five 0.2b is released. Download it here:
http://codespeak.net/z3/five/release/Five-0.2b.tgz
2004-07-30 -- We have released Five 0.1! Download it here:
http://codespeak.net/z3/five/release/Five-0.1.tgz
Joining the project
-------------------
Five is kindly hosted on codespeak.net, and is part of the larger
*Zope 3 Base* project that offers an approachable area for
developers of Zope 3 related software.
Five has a mailing list:
http://codespeak.net/mailman/listinfo/z3-five
We're also active on IRC, at ``#z3-base`` on freenode.
Five is hosted in a subversion repository on codespeak.net. You can
browse this on the web here:
http://codespeak.net/svn/z3/Five/
You can check out Five using the following subversion command::
svn co http://codespeak.net/svn/z3/Five/trunk Five
There's also a checkins mailing list for the Z3 project, here:
http://codespeak.net/mailman/listinfo/z3-checkins
If you want checkin access, please join the z3-five mailing list or
the ``#z3-base`` IRC channel, and ask us there.
We hope to hear from you!
===========
Five Manual
===========
Introduction
------------
Five's goal is to let you, the Zope 2 developer, use Zope 3 code in
Zope 2. Our aim is to make as much of Zope 3 code work in Zope 2 as
possible, while integrating it with Zope 2.
Five can be used inside your current Zope 2 project. The benefits are:
* availability of Zope 3 technologies in Zope 2 like the component
architecture and declarative configuration.
* you can gradually evolve your Zope 2 project so it is better
positioned for the migration to Zope 3.
* you start learning about Zope 3 right now, preparing yourself better
for the future. Since Zope 3 is open to contributions, you could
even influence your future for the better.
Five can also be used to develop new Zope 2 products, though depending
on your deployment requirements it might in that case make more sense
to develop for Zope 3 directly.
Five is only useful on the Python (Product) level in Zope 2, not from
within the Zope Management Interface. Five makes no attempt to provide
a user interface, but is aimed squarely at the Python developer.
Zope 3 interfaces
-----------------
Interfaces?
===========
An interface is simply a description of what an object provides to the
world, i.e. its public attribute and methods. It looks very much like
a class, but contains no implementation::
from zope.interface import Interface
# by convention, all interfaces are prefixed with ``I``
class IElephant(Interface):
"""An elephant is a big object that barely fits in the cupboard.
"""
def getAngerLevel():
"""Anger level, maximum of 100.
The longer the elephant has been in the cupboard, the angrier.
"""
def isInCupboard():
"""Returns true if the elephant is indeed in cupboard.
"""
def trunkSmash(target):
"""Smash the target with trunk.
The anger level determines the force of the hit.
"""
def trample(target):
"""Trample the target.
The anger level determines the rate of flattening of the target.
"""
A concrete class somewhere can now claim that it implements the
interface (i.e. its instance will provide the interface)::
class PinkElephant:
# this says all instances of this class provide IElephant
implements(IElephant)
def getAngerLevel(self):
return 0 # this elephant is peaceful
def isInCupboard(self):
return False # it's never in a cupboard but can be found in bottles
def trunkSmash(self, target):
target.tickle()
def trample(self, target):
target.patOnHead()
Interfaces themselves are good for a number of reasons:
* They provide API documentation.
* They help you make explicit the design of your application,
hopefully improving it.
* If an object provides an interface, that object is considered to be
a *component*. This means you can use Zope 3's component
architecture with these objects.
In order to use Five, you'll have to make your objects provide
interfaces. Sometimes, you cannot change the code of class (as you are
not the maintainer), but you still want to make it implement an
interface. Five provides a ZCML directive to do this::
<five:implements class="tolkien.Oliphant"
implements="interfaces.IElephant" />
Interfaces in Zope 2 versus Zope 3
==================================
You may be familiar with Zope 2's way of declaring interfaces. Zope 2
has used the ``__implements__`` class attribute for interface
declarations. Zope 2 cannot detect Zope 3 interfaces and the Zope 3
machinery cannot detect Zope 2 interfaces. This is a good thing, as
Zope 2 has no way to deal with Zope 3 interfaces, and Zope 3 cannot
comprehend Zope 2 interfaces. This means you can safely make a class
declare both a Zope 2 and Zope 3 interface independently from each
other. It's a rare case where you need this though; you're usually
better off just switching to ``implements()`` for your application if
you are using Five.
Switching from Zope 2 interfaces to Zope 3 interfaces is easy -- just
make your interfaces inherit from ``zope.interface.Interface`` instead
of ``Interface.Interface`` (or ``Interface.Base``). Next, change all
``__implements__`` to ``implements()``.
This should get you going and your application may very well still
work. Later on, you will also have to change calls to
``isImplementedBy`` and such in your application to ``providedBy``, as
``isImplementedBy`` has been deprecated (you'll see the
DeprecationWarnings in your Zope log).
Adapters
--------
From a Python programmer's perspective, the immediate thing that Five
brings to do the table are adapters. This section goes through some
demo code to explain how everything is tied
together. ``demo/FiveDemo`` is a demo Product you can install and
examine that has all the presented here together.
Zope 3 adapters depend on Zope 3 interfaces. To create a Zope 3
interface you need to subclass it from
``zope.interface.Interface``. Here is an example::
from zope.interface import Interface
class IMyInterface(Interface):
"""This is a Zope 3 interface.
"""
def someMethod():
"""This method does amazing stuff.
"""
Now to make some class declare that it implements this interface, you
need to use the ``implements()`` function in the class::
from zope.interface import implements
from interfaces import IMyInterface
class MyClass:
implements(IMyInterface)
def someMethod(self):
return "I am alive! Alive!"
For an explanation of the relation of Zope 3 interfaces to Zope 2
interfaces, see below.
Now let's set up the interface that we are adapting to::
class INewInterface(Interface):
"""The interface we adapt to.
"""
def anotherMethod():
"""This method does more stuff.
"""
Next we'll work on the class that implements the adapter. The
requirement to make a class that is an adapter is very simple; you
only need to take a context object as the constructor. The context
object is the object being adapted. An example::
from zope.interface import implements
from interfaces import INewInterface
class MyAdapter:
implements(INewInterface)
def __init__(self, context):
self.context = context
def anotherMethod(self):
return "We have adapted: %s" % self.context.someMethod()
Next, we hook it all up using zcml. If the classes are in a module
called ``classes.py`` and the interfaces in a module called
``interfaces.py``, we can declare ``MyAdapter`` to be an adapter for
``IMyInterface`` to ``INewInterface`` like this (in a file called
``configure.zcml``)::
<configure xmlns="http://namespaces.zope.org/zope">
<adapter
for=".interfaces.IMyInterface"
provides=".interfaces.INewInterface"
factory=".classes.MyAdapter" />
</configure>
Five will automatically pickup ``configure.zcml`` when it's placed in
the product's directory. Any object that provides ``INewInterface``
can now be adapted to ``INewInterface``, like this::
from classes import MyClass
from interfaces import INewInterface
object = MyClass()
adapted = INewInterface(object)
print adapted.anotherMethod()
Views in Five
-------------
This section will give a brief introduction on how to use the five
view system. ``demo/FiveViewsDemo`` is a demo Product you can install
and examine that has all the presented here tied together, please
consult it for more details. ``tests/products/FiveTest`` actually
contains a more detailed set of test views, trying a number of
features. Finally, read up on the way Zope 3 does it. While Five is a
subset of Zope 3 functionality and has been adapted to work with Zope
2, much of Zope 3's documentation still works.
Five enables you to create views for your own objects, or even built-in
Zope objects, as long as two things are the case:
* The object provides an Zope 3 interface, typically through its class.
* The object (typically its class) is made Zope 3 traversable. This
allows Zope 3 views, resources and other things to be attached to a
Zope 2 object.
Typically you give your classes an interface using the ``implements``
directive in the class body::
class MyClass:
implements(ISomeInterface)
For existing objects that you cannot modify this is not
possible. Instead, we provide a ZCML directive to accomplish this. As
an example, to make Zope's ``Folder`` (and all its subclasses)
implement ``IFolder`` (an interface you defined), you can do the
following in ZCML::
<five:implements class="OFS.Folder.Folder"
interface=".interfaces.IFolder" />
``five`` in this case refers to the XML namespace for Five,
``http://namespace.zope.org/five``.
We've provided another ZCML directive to make an object
traversable. To make your MyClass traversable, let's assume it is in
``mymodule``, in the same package as the zcml file we are editing::
<five:traversable class=".mymodule.MyClass" />
To continue our example, to make Zope's ``Folder`` traversable through
Five, you need to declare this in ZCML as well:
<five:traversable class="OFS.Folder.Folder"/>
This makes Folder traverse in the Zope 3 way first, looking up views
and other things, and then if they cannot be found, fall back on the
regular Zope 2 traversal. It does this by overriding the
``__bobo_traverse__`` hook. Old hooks that are already in place in an
object will be stored and become the secondary fallback. This allows
the ZMI to work still, but new views can be added on the fly.
Note that at the point of writing it is only possible to make an object
viewable through ZCML if this object does not already provide its own
``__bobo_traverse__`` method.
Views in Five are simple classes. The only requirements for a Five
view class are:
* They need an ``__init__()`` that take a context and a request
attribute. Typically this comes from a base class, such as
``BrowserView``.
* They need to be initialized with the Zope 2 security system, as
otherwise you cannot use the view.
* This also means they need to be part of the Zope 2 acquisition
system, as this is a requirement for Zope 2 security to
function. The ``BrowserView`` base class, available from
``Products.Five``, already inherits from ``Acquisition.Explicit`` to
make this be the case. Acquisition is explicit so no attributes can
be acquired by accident.
An example of a simple view::
from Products.Five import BrowserView
class SimpleFolderView(BrowserView):
security = ClassSecurityInfo()
security.declarePublic('eagle')
def eagle(self):
"""Test
"""
return "The eagle has landed: %s" % self.context.objectIds()
InitializeClass(SimpleFolderView)
Note that it is not a good idea to give a view class its own
``index_html``, as this confuses Five's view lookup machinery.
As you can see, the class is initialized with the Zope 2 security
system. This view uses methods in Python, but you can also use other
Zope 2 mechanisms such as ``PageTemplateFile``.
Finally, we need to hook up the pages through ZCML::
<browser:page
for=".interfaces.IFolder"
class=".browser.SimpleFolderView"
attribute="eagle"
name="eagle.txt"
permission="zope2.ViewManagementScreens"
/>
``browser`` in this refers to the XML namespace of Zope 3 for browser
related things; it's
``http://namespace.zope.org/browser``. ``permission`` declares the
Zope 2 permission needs in order to access this view. The file
``permissions.zcml`` in Five contains a mapping of Zope 2 permissions
to their Zope 3 names.
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser"
xmlns:five="http://namespaces.zope.org/five"
>
<five:traversable class="OFS.Folder.Folder" />
<browser:resource
image="z3base.png"
name="z3base.png"
permission="zope2.ViewManagementScreens"
/>
</configure>
import module, other
def initialize(context):
print "*" * 70
module.demo_manual_adaptation()
other.demo_animal_farm()
print "*" * 70
<configure xmlns="http://namespaces.zope.org/zope">
<adapter
for=".module.IElephant"
provides=".module.INoiseMaker"
factory=".module.ElephantNoiseMaker" />
<adapter
for=".other.IChicken"
provides=".module.INoiseMaker"
factory=".other.ChickenNoiseMaker" />
</configure>
from zope.interface import Interface, implements
class IElephant(Interface):
"""An elephant is a big grey animal.
"""
def getAngerLevel():
"Return anger level on scale 0 (placid) to 10 (raging)"
def trample(target):
"Trample the target."
def trumpet():
"Make loud noise with trunk."
def terribleRacket():
return "A terrible racket"
class AfricanElephant:
implements(IElephant)
def getAngerLevel(self):
return 5 # always pretty stroppy
def trample(self, target):
target.flatten()
def trumpet(self):
return "A terrible racket"
class INoiseMaker(Interface):
"""Something that makes noise.
"""
def makeNoise():
"Returns the noise that's made."
class ElephantNoiseMaker:
"""Adapts elephant to noise maker.
"""
implements(INoiseMaker)
def __init__(self, context):
self.context = context
def makeNoise(self):
return self.context.trumpet()
def demo_manual_adaptation():
elephant = AfricanElephant()
noise_maker = ElephantNoiseMaker(elephant)
print noise_maker.makeNoise()
from zope.interface import Interface, implements
from module import INoiseMaker, IElephant, AfricanElephant
class IChicken(Interface):
def getConfusionLevel():
"Get the confusion level of this chicken, 0 asleep, 10 frantic"
def cluck():
"""Return clucking sound of the chicken.
"""
class Chicken:
implements(IChicken)
def __init__(self, confusion_level):
self._confusion_level = confusion_level
def getConfusionLevel(self):
return self._confusion_level
def cluck(self):
return ' '.join(["cluck"] * self.getConfusionLevel())
class IndianElephant:
implements(IElephant)
def __init__(self, anger_level):
self._anger_level = anger_level
def hit(self):
"""Hit the indian elephant with a stick.
"""
if self._anger_level <= 10:
self._anger_level += 1
def getAngerLevel(self):
return self._anger_level
def trumpet(self):
return "t" + ("o" * self._anger_level) + "t"
class ChickenNoiseMaker:
implements(INoiseMaker)
def __init__(self, context):
self.context = context
def makeNoise(self):
return self.context.cluck()
def demo_animal_farm():
animal_farm = [Chicken(5), AfricanElephant(),
Chicken(3), IndianElephant(3)]
for animal in animal_farm:
noise_maker = INoiseMaker(animal)
print noise_maker.makeNoise()
This directory contains Five tutorial products.
import democontent
def initialize(context):
context.registerClass(
democontent.DemoContent,
constructors = (democontent.manage_addDemoContentForm,
democontent.manage_addDemoContent),
)
from Products.Five import BrowserView
import random
class Overview(BrowserView):
def reversedIds(self):
result = []
for id in self.context.objectIds():
l = list(id)
l.reverse()
reversed_id = ''.join(l)
result.append(reversed_id)
return result
def directlyPublished(self):
return "This is directly published"
class NewExample(BrowserView):
def helpsWithOne(self):
return random.randrange(10)
def two(self):
return "Two got called"
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser"
xmlns:five="http://namespaces.zope.org/five">
<five:traversable
class="OFS.Folder.Folder"
/>
<browser:page
for="Products.Five.interfaces.IFolder"
name="overview.html"
template="overview.pt"
permission="zope2.ViewManagementScreens"
/>
<browser:page
for="Products.Five.interfaces.IFolder"
name="overview2.html"
template="overview2.pt"
permission="zope2.ViewManagementScreens"
class=".browser.Overview"
/>
<browser:page
for="Products.Five.interfaces.IFolder"
name="test.html"
class=".browser.Overview"
attribute="directlyPublished"
permission="zope2.ViewManagementScreens"
/>
<browser:pages
for="Products.Five.interfaces.IFolder"
class=".browser.NewExample"
permission="zope2.ViewManagementScreens"
>
<browser:page
name="one.html"
template="one.pt"
/>
<browser:page
name="two.html"
attribute="two"
/>
</browser:pages>
<five:traversable class=".democontent.DemoContent" />
<browser:page
for=".democontent.IDemoContent"
name="someview.html"
template="someview.pt"
permission="zope2.ViewManagementScreens"
/>
<five:defaultViewable class=".democontent.DemoContent" />
<browser:defaultView
for=".democontent.IDemoContent"
name="someview.html" />
</configure>
from zope.interface import Interface, implements
from OFS.SimpleItem import SimpleItem
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
class IDemoContent(Interface):
def mymethod():
"Return some text"
class DemoContent(SimpleItem):
implements(IDemoContent)
meta_type = 'Five Demo Content'
def __init__(self, id, title):
self.id = id
self.title = title
def mymethod(self):
return "Hello world"
manage_addDemoContentForm = PageTemplateFile(
"www/demoContentAdd", globals(),
__name__ = 'manage_addDemoContentForm')
def manage_addDemoContent(self, id, title, REQUEST=None):
"""Add the demo content."""
id = self._setObject(id, DemoContent(id, title))
if REQUEST is None:
return
REQUEST.RESPONSE.redirect(REQUEST['URL1'] + '/manage_main')
<html>
<body>
<p>The random number is <span tal:replace="view/helpsWithOne">0</span></p>
</body>
</html>
<html>
<body>
<p tal:content="context/objectIds"></p>
</body>
</html>
<html>
<body>
<p tal:content="view/reversedIds"></p>
</body>
</html>
<html>
<body>
<p>Some view</p>
</body>
</html>
<h1 tal:replace="structure here/manage_page_header">Header</h1>
<h2 tal:define="form_title string:Add Demo Content"
tal:replace="structure here/manage_form_title">Form Title</h2>
<p class="form-help">
Add Demo Content
</p>
<form action="manage_addDemoContent" method="post">
<table cellspacing="0" cellpadding="2" border="0">
<tr>
<td align="left" valign="top">
<div class="form-label">
Id
</div>
</td>
<td align="left" valign="top">
<input type="text" name="id" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Title
</div>
</td>
<td align="left" valign="top">
<input type="text" name="title" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
</td>
<td align="left" valign="top">
<div class="form-element">
<input class="form-element" type="submit" name="submit_add"
value=" Add " />
</div>
</td>
</tr>
</table>
</form>
<h1 tal:replace="structure here/manage_page_footer">Footer</h1>
<tal:tag condition="view/update"/>
<html metal:use-macro="here/five_template/macros/master">
<body metal:fill-slot="main">
<div>
<div metal:define-macro="body">
<form action="." tal:attributes="action request/URL" method="POST"
enctype="multipart/form-data">
<div metal:define-macro="formbody">
<h3 tal:condition="view/label"
tal:content="view/label"
metal:define-slot="heading"
>Edit something</h3>
<p tal:define="status view/update"
tal:condition="status"
tal:content="status" />
<p tal:condition="view/errors" i18n:translate="">
There are <strong tal:content="python:len(view.errors)"
i18n:name="num_errors">6</strong> input errors.
</p>
<div metal:define-slot="extra_info" tal:replace="nothing">
</div>
<div class="row"
metal:define-slot="extra_top" tal:replace="nothing">
<div class="label">Extra top</div>
<div class="field"><input type="text" style="width:100%" /></div>
</div>
<div metal:use-macro="context/@@widget_macros/widget_rows" />
<div class="separator"></div>
<div class="row"
metal:define-slot="extra_bottom" tal:replace="nothing">
<div class="label">Extra bottom</div>
<div class="field"><input type="text" style="width:100%" /></div>
</div>
<div class="separator"></div>
</div>
<div class="row">
<div class="controls">
<input type="submit" value="Refresh"
i18n:attributes="value refresh-button" />
<input type="submit" name="UPDATE_SUBMIT" value="Change"
i18n:attributes="value submit-button"/>
</div>
</div>
<div class="row" metal:define-slot="extra_buttons" tal:replace="nothing">
</div>
<div class="separator"></div>
</form>
</div>
</div>
</body>
</html>
"""
Use 'structured monkey patching' to enable zope.app.container event sending for
Zope 2 objects.
"""
from Products.Five.fiveconfigure import isFiveMethod
from zope.event import notify
from zope.interface import implements
from zope.app.container.interfaces import IObjectAddedEvent,\
IObjectRemovedEvent
from zope.app.container.contained import ObjectMovedEvent
from zope.app.event.objectevent import ObjectCopiedEvent
# ObjectAddedEvent and ObjectRemovedEvent are different in Zope 2
class ObjectAddedEvent(ObjectMovedEvent):
implements(IObjectAddedEvent)
def __init__(self, object, newParent=None, newName=None):
if newParent is None:
newParent = object.aq_inner.aq_parent
if newName is None:
newName = object.id
ObjectMovedEvent.__init__(self, object, None, None, newParent, newName)
class ObjectRemovedEvent(ObjectMovedEvent):
implements(IObjectRemovedEvent)
def __init__(self, object, oldParent=None, oldName=None):
if oldParent is None:
oldParent = object.aq_inner.aq_parent
if oldName is None:
oldName = object.id
ObjectMovedEvent.__init__(self, object, oldParent, oldName, None, None)
def manage_afterAdd(self, item, container):
original_location_path = getattr(self, '__five_location_path__', None)
self.__five_location_path__ = self.getPhysicalPath()
# if there still is an object in the original location, we're copied
# we cannot rely on manage_afterClone, as this gets triggered only
# *after* a manage_afterAdd. This logic might fail in the case where
# something *is* somehow left in the original location that can
# be traversed to.
is_copied = original_location_path and (self.unrestrictedTraverse(
original_location_path, None) is not None)
if is_copied:
notify(ObjectCopiedEvent(self))
if original_location_path is None or is_copied:
notify(ObjectAddedEvent(self))
else:
original_location = self.unrestrictedTraverse(
original_location_path[:-1])
notify(ObjectMovedEvent(self,
original_location, original_location_path[-1],
container, self.id))
# call original
method = getattr(self, '__five_original_manage_afterAdd', None)
if method is not None:
self.__five_original_manage_afterAdd(item, container)
manage_afterAdd.__five_method__ = None
def manage_beforeDelete(self, item, container):
notify(ObjectRemovedEvent(self))
# call original
method = getattr(self, '__five_manage_beforeDelete', None)
if method is not None:
self._five_original_manage_beforeDelete(item, container)
manage_beforeDelete.__five_method__ = None
def classSendEvents(class_):
"""Make instances of the class send Object*Event.
"""
# tuck away original methods if necessary
for name in ['manage_afterAdd', 'manage_beforeDelete']:
method = getattr(class_, name, None)
if not isFiveMethod(method):
# if we haven't alread overridden this, tuck away originals
setattr(class_, '__five_original_' + name, method)
class_.manage_afterAdd = manage_afterAdd
class_.manage_beforeDelete = manage_beforeDelete
def sendEvents(_context, class_):
_context.action(
discriminator = ('five:sendEvents', class_),
callable = classSendEvents,
args=(class_,)
)
<html metal:define-macro="master">
<head>
<metal:block define-slot="css_slot">
</metal:block>
</head>
<body>
<metal:block define-slot="main">
</metal:block>
</body>
</html>
##############################################################################
#
# Copyright (c) 2004 Five Contributors. All rights reserved.
#
# This software is distributed under the terms of the Zope Public
# License (ZPL) v2.1. See COPYING.txt for more information.
#
##############################################################################
"""Five-specific directive handlers
These directives are specific to Five and have no equivalents in Zope 3.
$Id: fiveconfigure.py 8255 2005-01-13 14:05:46Z regebro $
"""
import os
import glob
import warnings
from zope.interface import classImplements
from zope.configuration import xmlconfig
from zope.app.component.interface import provideInterface
from viewable import Viewable
from traversable import Traversable
from bridge import fromZ2Interface
from browserconfigure import page
def findProducts():
import Products
from types import ModuleType
products = []
for name in dir(Products):
product = getattr(Products, name)
if isinstance(product, ModuleType) and hasattr(product, '__file__'):
products.append(product)
return products
def loadProducts(_context):
products = findProducts()
# first load meta.zcml files
for product in products:
zcml = os.path.join(os.path.dirname(product.__file__), 'meta.zcml')
if os.path.isfile(zcml):
xmlconfig.include(_context, zcml, package=product)
# now load their configure.zcml
for product in products:
zcml = os.path.join(os.path.dirname(product.__file__),
'configure.zcml')
if os.path.isfile(zcml):
xmlconfig.include(_context, zcml, package=product)
def loadProductsOverrides(_context):
for product in findProducts():
zcml = os.path.join(os.path.dirname(product.__file__),
'overrides.zcml')
if os.path.isfile(zcml):
xmlconfig.includeOverrides(_context, zcml, package=product)
def implements(_context, class_, interface):
for interface in interface:
_context.action(
discriminator = None,
callable = classImplements,
args = (class_, interface)
)
_context.action(
discriminator = None,
callable = provideInterface,
args = (interface.__module__ + '.' + interface.getName(),
interface)
)
def isFiveMethod(m):
return hasattr(m, '__five_method__')
def classTraversable(class_):
# If a class already has this attribute, it means it is either a
# subclass of Traversable or was already processed with this
# directive; in either case, do nothing... except in the case were
# the class overrides __bobo_traverse__ instead of getting it from
# a base class. In this case, we suppose that the class probably
# didn't bother with the base classes __bobo_traverse__ anyway and
# we step __fallback_traverse__.
if hasattr(class_, '__five_traversable__'):
if (hasattr(class_, '__bobo_traverse__') and
isFiveMethod(class_.__bobo_traverse__)):
return
if hasattr(class_, '__bobo_traverse__'):
if not isFiveMethod(class_.__bobo_traverse__):
# if there's an existing bobo_traverse hook already, use that
# as the traversal fallback method
setattr(class_, '__fallback_traverse__', class_.__bobo_traverse__)
if not hasattr(class_, '__fallback_traverse__'):
setattr(class_, '__fallback_traverse__',
Traversable.__fallback_traverse__.im_func)
setattr(class_, '__bobo_traverse__',
Traversable.__bobo_traverse__.im_func)
setattr(class_, '__five_traversable__', True)
def traversable(_context, class_):
_context.action(
discriminator = None,
callable = classTraversable,
args = (class_,)
)
def classDefaultViewable(class_):
# If a class already has this attribute, it means it is either a
# subclass of DefaultViewable or was already processed with this
# directive; in either case, do nothing... except in the case were
# the class overrides the attribute instead of getting it from
# a base class. In this case, we suppose that the class probably
# didn't bother with the base classes attribute anyway.
if hasattr(class_, '__five_viewable__'):
if (hasattr(class_, '__browser_default__') and
isFiveMethod(class_.__browser_default__)):
return
if hasattr(class_, '__browser_default__'):
# if there's an existing __browser_default__ hook already, use that
# as the fallback
if not isFiveMethod(class_.__browser_default__):
setattr(class_, '__fallback_default__', class_.__browser_default__)
if not hasattr(class_, '__fallback_default__'):
setattr(class_, '__fallback_default__',
Viewable.__fallback_default__.im_func)
setattr(class_, '__browser_default__',
Viewable.__browser_default__.im_func)
setattr(class_, '__five_viewable__', True)
def defaultViewable(_context, class_):
_context.action(
discriminator = None,
callable = classDefaultViewable,
args = (class_,)
)
def viewable(_context, class_):
# XXX do not need to mark where this is used, as simple search
# should find all instances easily
warnings.warn(
'The five:viewable directive has been deprecated. '
'Please use the five:traversable directive instead.',
DeprecationWarning)
_context.action(
discriminator = None,
callable = classTraversable,
args=(class_,)
)
def createZope2Bridge(zope2, package, name):
# Map a Zope 2 interface into a Zope3 interface, seated within 'package'
# as 'name'.
z3i = fromZ2Interface(zope2)
if name is not None:
z3i.__dict__['__name__'] = name
z3i.__dict__['__module__'] = package.__name__
setattr(package, z3i.getName(), z3i)
def bridge(_context, zope2, package, name=None):
# Directive handler for <five:bridge> directive.
# N.B.: We have to do the work early, or else we won't be able
# to use the synthesized interface in other ZCML directives.
createZope2Bridge(zope2, package, name)
# Faux action, only for conflict resolution.
_context.action(
discriminator = (zope2,),
)
def pagesFromDirectory(_context, directory, module, for_=None,
layer='default', permission='zope.Public'):
if isinstance(module, basestring):
module = _context.resolve(module)
_prefix = os.path.dirname(module.__file__)
directory = os.path.join(_prefix, directory)
if not os.path.isdir(directory):
raise ConfigurationError(
"Directory %s does not exist" % directory
)
for fname in glob.glob(os.path.join(directory, '*.pt')):
name = os.path.splitext(os.path.basename(fname))[0]
page(_context, name=name, permission=permission,
layer=layer, for_=for_, template=fname)
##############################################################################
#
# Copyright (c) 2004 Five Contributors. All rights reserved.
#
# This software is distributed under the terms of the Zope Public
# License (ZPL) v2.1. See COPYING.txt for more information.
#
##############################################################################
"""Five ZCML directive schemas
$Id: fivedirectives.py 8255 2005-01-13 14:05:46Z regebro $
"""
from zope.interface import Interface
from zope.app.publisher.browser.metadirectives import IBasicResourceInformation
from zope.configuration.fields import GlobalObject, Tokens, PythonIdentifier
from zope.schema import TextLine
class IImplementsDirective(Interface):
"""State that a class implements something.
"""
class_ = GlobalObject(
title=u"Class",
required=True
)
interface = Tokens(
title=u"One or more interfaces",
required=True,
value_type=GlobalObject()
)
class ITraversableDirective(Interface):
"""Make instances of class traversable publically.
This can be used to browse to pages, resources, etc.
Traversal can be controlled by registering an ITraverser adapter.
"""
class_ = GlobalObject(
title=u"Class",
required=True
)
class IDefaultViewableDirective(Interface):
"""Make instances of class viewable publically.
The default view is looked up using a IBrowserDefault adapter.
"""
class_ = GlobalObject(
title=u"Class",
required=True
)
class ISendEventsDirective(Interface):
"""Make instances of class send events.
"""
class_ = GlobalObject(
title=u"Class",
required=True
)
class IBridgeDirective(Interface):
"""Bridge from a Zope 2 interface to an equivalent Zope3 interface.
"""
zope2 = GlobalObject(
title=u"Zope2",
required=True
)
package = GlobalObject(
title=u"Target package",
required=True
)
name = PythonIdentifier(
title=u"Zope3 Interface name",
description=u"If not supplied, the new interface will have the same "
u"name as the source interface.",
required=False
)
class IPagesFromDirectoryDirective(IBasicResourceInformation):
"""Register each file in a skin directory as a page resource
"""
for_ = GlobalObject(
title=u"The interface this view is for.",
required=False
)
module = GlobalObject(
title=u"Module",
required=True
)
directory = TextLine(
title=u"Directory",
description=u"The directory containing the resource data.",
required=True
)
##############################################################################
#
# Copyright (c) 2000-2003 Zope Corporation and Contributors.
# Copyright (c) 2004 Five Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Five interfaces
$Id: interfaces.py 8253 2005-01-13 12:49:07Z regebro $
"""
from zope.interface import Interface, Attribute
from zope.interface.interfaces import IInterface
from zope.schema import Bool, BytesLine, Tuple
try:
from persistent.interfaces import IPersistent
except ImportError:
class IPersistent(Interface):
"""Persistent object"""
class IPersistentExtra(Interface):
def bobobase_modification_time():
""" """
def locked_in_version():
"""Was the object modified in any version?"""
def modified_in_version():
"""Was the object modified in this version?"""
class IBrowserDefault(Interface):
"""Provide a hook for deciding about the default view for an object"""
def defaultView(self, request):
"""Return the object to be published
(usually self) and a sequence of names to traverse to
find the method to be published.
"""
class IAcquisitionWrapper(Interface):
def acquire(name, filter=0, extra=None, expl=0, default=0,
explicit=1, containment=0):
"""Get an attribute, acquiring it if necessary"""
aq_acquire = acquire
def aq_inContextOf(obj, inner=1):
"""Test whether the object is currently in the context of the
argument"""
class IAcquisition(Interface):
def __of__(context):
"""Return the object in a context"""
def aq_acquire(name, filter=None, extra=None, explicit=None):
"""Get an attribute, acquiring it if necessary"""
def aq_get(name, default=None):
"""Get an attribute, acquiring it if necessary."""
# those are computed attributes, aren't they?
def aq_base():
"""Get the object unwrapped"""
def aq_parent():
"""Get the parent of an object"""
def aq_self():
"""Get the object with the outermost wrapper removed"""
def aq_inner():
"""Get the object with alll but the innermost wrapper removed"""
def aq_chain(containment=0):
"""Get a list of objects in the acquisition environment"""
class IManageable(Interface):
"""Something that is manageable in the ZMI"""
def manage(URL1):
"""Show management screen"""
def manage_afterAdd(item, container):
"""Gets called after being added to a container"""
def manage_beforeDelete(item, container):
"""Gets called before being deleted"""
def manage_afterClone(item):
"""Gets called after being cloned"""
def manage_editedDialog(REQUEST, **args):
"""Show an 'edited' dialog"""
def filtered_manage_options(REQUEST=None):
""" """
def manage_workspace():
"""Dispatch to first interface in manage_options"""
def tabs_path_default(REQUEST):
""" """
def tabs_path_info(script, path,):
""" """
def class_manage_path(self):
""" """
manage_options = Tuple(
title=u"Manage options",
)
manage_tabs = Attribute("""Management tabs""")
class IFTPAccess(Interface):
"""Provide support for FTP access"""
def manage_FTPstat(REQUEST):
"""Returns a stat-like tuple. (marshalled to a string) Used by
FTP for directory listings, and MDTM and SIZE"""
def manage_FTPlist(REQUEST):
"""Returns a directory listing consisting of a tuple of
(id,stat) tuples, marshaled to a string. Note, the listing it
should include '..' if there is a Folder above the current
one.
In the case of non-foldoid objects it should return a single
tuple (id,stat) representing itself."""
class IWriteLock(Interface):
"""This represents the basic protocol needed to support the write lock
machinery.
It must be able to answer the questions:
o Is the object locked?
o Is the lock owned by the current user?
o What lock tokens are associated with the current object?
o What is their state (how long until they're supposed to time out?,
what is their depth? what type are they?
And it must be able to do the following:
o Grant a write lock on the object to a specified user.
- *If lock depth is infinite, this must also grant locks on **all**
subobjects, or fail altogether*
o Revoke a lock on the object.
- *If lock depth is infinite, this must also revoke locks on all
subobjects*
**All methods in the WriteLock interface that deal with checking valid
locks MUST check the timeout values on the lockitem (ie, by calling
'lockitem.isValid()'), and DELETE the lock if it is no longer valid**
"""
def wl_lockItems(killinvalids=0):
""" Returns (key, value) pairs of locktoken, lock.
if 'killinvalids' is true, invalid locks (locks whose timeout
has been exceeded) will be deleted"""
def wl_lockValues(killinvalids=0):
""" Returns a sequence of locks. if 'killinvalids' is true,
invalid locks will be deleted"""
def wl_lockTokens(killinvalids=0):
""" Returns a sequence of lock tokens. if 'killinvalids' is true,
invalid locks will be deleted"""
def wl_hasLock(token, killinvalids=0):
""" Returns true if the lock identified by the token is attached
to the object. """
def wl_isLocked():
""" Returns true if 'self' is locked at all. If invalid locks
still exist, they should be deleted."""
def wl_setLock(locktoken, lock):
""" Store the LockItem, 'lock'. The locktoken will be used to fetch
and delete the lock. If the lock exists, this MUST
overwrite it if all of the values except for the 'timeout' on the
old and new lock are the same. """
def wl_getLock(locktoken):
""" Returns the locktoken identified by the locktokenuri """
def wl_delLock(locktoken):
""" Deletes the locktoken identified by the locktokenuri """
def wl_clearLocks():
""" Deletes ALL DAV locks on the object - should only be called
by lock management machinery. """
class IDAVResource(IWriteLock):
"""Provide basic WebDAV support for non-collection objects."""
__dav_resource__ = Bool(
title=u"Is DAV resource"
)
__http_methods__ = Tuple(
title=u"HTTP methods",
description=u"Sequence of valid HTTP methods"
)
def dav__init(request, response):
"""
Init expected HTTP 1.1 / WebDAV headers which are not
currently set by the base response object automagically.
Note we set an borg-specific header for ie5 :( Also, we sniff
for a ZServer response object, because we don't want to write
duplicate headers (since ZS writes Date and Connection
itself)."""
def dav__validate(object, methodname, REQUEST):
""" """
def dav__simpleifhandler(request, response, method='PUT',
col=0, url=None, refresh=0):
""" """
def HEAD(EQUEST, RESPONSE):
"""Retrieve resource information without a response body."""
def PUT(REQUEST, RESPONSE):
"""Replace the GET response entity of an existing resource.
Because this is often object-dependent, objects which handle
PUT should override the default PUT implementation with an
object-specific implementation. By default, PUT requests
fail with a 405 (Method Not Allowed)."""
def OPTIONS(REQUEST, RESPONSE):
"""Retrieve communication options."""
def TRACE(REQUEST, RESPONSE):
"""Return the HTTP message received back to the client as the
entity-body of a 200 (OK) response. This will often usually
be intercepted by the web server in use. If not, the TRACE
request will fail with a 405 (Method Not Allowed), since it
is not often possible to reproduce the HTTP request verbatim
from within the Zope environment."""
def DELETE(REQUEST, RESPONSE):
"""Delete a resource. For non-collection resources, DELETE may
return either 200 or 204 (No Content) to indicate success."""
def PROPFIND(REQUEST, RESPONSE):
"""Retrieve properties defined on the resource."""
def PROPPATCH(self, REQUEST, RESPONSE):
"""Set and/or remove properties defined on the resource."""
def MKCOL(REQUEST, RESPONSE):
"""Create a new collection resource. If called on an existing
resource, MKCOL must fail with 405 (Method Not Allowed)."""
def COPY(REQUEST, RESPONSE):
"""Create a duplicate of the source resource whose state
and behavior match that of the source resource as closely
as possible. Though we may later try to make a copy appear
seamless across namespaces (e.g. from Zope to Apache), COPY
is currently only supported within the Zope namespace."""
def MOVE(REQUEST, RESPONSE):
"""Move a resource to a new location. Though we may later try to
make a move appear seamless across namespaces (e.g. from Zope
to Apache), MOVE is currently only supported within the Zope
namespace."""
def LOCK(REQUEST, RESPONSE):
"""Lock a resource"""
def UNLOCK(REQUEST, RESPONSE):
"""Remove an existing lock on a resource."""
def manage_DAVget():
"""Gets the document source"""
def listDAVObjects():
""" """
class ICopySource(Interface):
"""Interface for objects which allow themselves to be copied."""
def _canCopy(op=0):
"""Called to make sure this object is copyable. The op var
is 0 for a copy, 1 for a move."""
def _notifyOfCopyTo(container, op=0):
"""Overide this to be pickly about where you go! If you dont
want to go there, raise an exception. The op variable is
0 for a copy, 1 for a move."""
def _getCopy(container):
"""
Commit a subtransaction to:
1) Make sure the data about to be exported is current
2) Ensure self._p_jar and container._p_jar are set even if
either one is a new object
"""
def _postCopy(self, container, op=0):
"""Called after the copy is finished to accomodate special cases.
The op var is 0 for a copy, 1 for a move."""
def _setId(self, id):
"""Called to set the new id of a copied object."""
def cb_isCopyable(self):
"""Is object copyable? Returns 0 or 1"""
def cb_isMoveable(self):
"""Is object moveable? Returns 0 or 1"""
def cb_userHasCopyOrMovePermission(self):
""" """
class ITraversable(Interface):
def absolute_url(relative=0):
"""Return the absolute URL of the object.
This a canonical URL based on the object's physical
containment path. It is affected by the virtual host
configuration, if any, and can be used by external
agents, such as a browser, to address the object.
If the relative argument is provided, with a true value, then
the value of virtual_url_path() is returned.
Some Products incorrectly use '/'+absolute_url(1) as an
absolute-path reference. This breaks in certain virtual
hosting situations, and should be changed to use
absolute_url_path() instead.
"""
def absolute_url_path():
"""Return the path portion of the absolute URL of the object.
This includes the leading slash, and can be used as an
'absolute-path reference' as defined in RFC 2396.
"""
def virtual_url_path():
"""Return a URL for the object, relative to the site root.
If a virtual host is configured, the URL is a path relative to
the virtual host's root object. Otherwise, it is the physical
path. In either case, the URL does not begin with a slash.
"""
def getPhysicalPath():
'''Returns a path (an immutable sequence of strings)
that can be used to access this object again
later, for example in a copy/paste operation. getPhysicalRoot()
and getPhysicalPath() are designed to operate together.
'''
def unrestrictedTraverse(path, default=None, restricted=0):
"""Lookup an object by path,
path -- The path to the object. May be a sequence of strings or a slash
separated string. If the path begins with an empty path element
(i.e., an empty string or a slash) then the lookup is performed
from the application root. Otherwise, the lookup is relative to
self. Two dots (..) as a path element indicates an upward traversal
to the acquisition parent.
default -- If provided, this is the value returned if the path cannot
be traversed for any reason (i.e., no object exists at that path or
the object is inaccessible).
restricted -- If false (default) then no security checking is performed.
If true, then all of the objects along the path are validated with
the security machinery. Usually invoked using restrictedTraverse().
"""
def restrictedTraverse(path, default=None):
"""Trusted code traversal code, always enforces security"""
class IOwned(Interface):
manage_owner = Attribute("""Manage owner view""")
def owner_info():
"""Get ownership info for display"""
def getOwner(info=0):
"""Get the owner
If a true argument is provided, then only the owner path and id are
returned. Otherwise, the owner object is returned.
"""
def getOwnerTuple():
"""Return a tuple, (userdb_path, user_id) for the owner.
o Ownership can be acquired, but only from the containment path.
o If unowned, return None.
"""
def getWrappedOwner():
"""Get the owner, modestly wrapped in the user folder.
o If the object is not owned, return None.
o If the owner's user database doesn't exist, return Nobody.
o If the owner ID does not exist in the user database, return Nobody.
"""
def changeOwnership(user, recursive=0):
"""Change the ownership to the given user. If 'recursive' is
true then also take ownership of all sub-objects, otherwise
sub-objects retain their ownership information."""
def userCanTakeOwnership(self):
""" """
def manage_takeOwnership(REQUEST, RESPONSE, recursive=0):
"""Take ownership (responsibility) for an object. If 'recursive'
is true, then also take ownership of all sub-objects.
"""
def manage_changeOwnershipType(explicit=1, RESPONSE=None, REQUEST=None):
"""Change the type (implicit or explicit) of ownership.
"""
def _deleteOwnershipAfterAdd(self):
""" """
def manage_fixupOwnershipAfterAdd(self):
""" """
class IUndoSupport(Interface):
manage_UndoForm = Attribute("""Manage Undo form""")
def get_request_var_or_attr(name, default):
""" """
def undoable_transactions(first_transaction=None,
last_transaction=None,
PrincipiaUndoBatchSize=None):
""" """
def manage_undo_transactions(transaction_info=(), REQUEST=None):
""" """
class IZopeObject(Interface):
isPrincipiaFolderish = Bool(
title=u"Is a folderish object",
description=u"Should be false for simple items",
)
meta_type = BytesLine(
title=u"Meta type",
description=u"The object's Zope2 meta type",
)
class IItem(IZopeObject, IManageable, IFTPAccess, IDAVResource,
ICopySource, ITraversable, IOwned, IUndoSupport):
__name__ = BytesLine(
title=u"Name"
)
title = BytesLine(
title=u"Title"
)
icon = BytesLine(
title=u"Icon",
description=u"Name of icon, relative to SOFTWARE_URL",
)
def getId():
"""Return the id of the object as a string.
This method should be used in preference to accessing an id
attribute of an object directly. The getId method is public.
"""
def _setId(id):
"""Set the id"""
def title_or_id():
"""Returns the title if it is not blank and the id otherwise."""
def title_and_id():
"""Returns the title if it is not blank and the id otherwise. If the
title is not blank, then the id is included in parens."""
def raise_standardErrorMessage(client=None, REQUEST={},
error_type=None, error_value=None, tb=None,
error_tb=None, error_message='',
tagSearch=None, error_log_url=''):
"""Raise standard error message"""
class IItemWithName(IItem):
"""Item with name"""
def getPhysicalPath():
"""Returns a path (an immutable sequence of strings) that can be used
to access this object again later, for example in a copy/paste
operation."""
class IPermissionMapping(Interface):
def manage_getPermissionMapping():
"""Return the permission mapping for the object
This is a list of dictionaries with:
permission_name -- The name of the native object permission
class_permission -- The class permission the permission is
mapped to.
"""
def manage_setPermissionMapping(permission_names=[],
class_permissions=[], REQUEST=None):
"""Change the permission mapping"""
class IRoleManager(IPermissionMapping):
"""An object that has configurable permissions"""
permissionMappingPossibleValues = Attribute("""Acquired attribute""")
def ac_inherited_permissions(all=0):
"""Get all permissions not defined in ourself that are inherited This
will be a sequence of tuples with a name as the first item and
an empty tuple as the second."""
def permission_settings(permission=None):
"""Return user-role permission settings. If 'permission' is passed to
the method then only the settings for 'permission' returned."""
manage_roleForm = Attribute(""" """)
def manage_role(role_to_manage, permissions=[], REQUEST=None):
"""Change the permissions given to the given role"""
manage_acquiredForm = Attribute(""" """)
def manage_acquiredPermissions(permissions=[], REQUEST=None):
"""Change the permissions that acquire"""
manage_permissionForm = Attribute(""" """)
def manage_permission(permission_to_manage, roles=[], acquire=0,
REQUEST=None):
"""Change the settings for the given permission
If optional arg acquire is true, then the roles for the permission
are acquired, in addition to the ones specified, otherwise the
permissions are restricted to only the designated roles."""
def manage_access(REQUEST, **kw):
"""Return an interface for making permissions settings"""
def manage_changePermissions(REQUEST):
"""Change all permissions settings, called by management screen"""
def permissionsOfRole(role):
"""used by management screen"""
def rolesOfPermission(permission):
"""used by management screen"""
def acquiredRolesAreUsedBy(permission):
"""used by management screen"""
# Local roles support
# -------------------
#
# Local roles allow a user to be given extra roles in the context
# of a particular object (and its children). When a user is given
# extra roles in a particular object, an entry for that user is made
# in the __ac_local_roles__ dict containing the extra roles.
__ac_local_roles__ = Attribute(""" """)
manage_listLocalRoles = Attribute(""" """)
manage_editLocalRoles = Attribute(""" """)
def has_local_roles():
""" """
def get_local_roles():
""" """
def users_with_local_role(role):
""" """
def get_valid_userids():
""" """
def get_local_roles_for_userid(userid):
""" """
def manage_addLocalRoles(userid, roles, REQUEST=None):
"""Set local roles for a user."""
def manage_setLocalRoles(userid, roles, REQUEST=None):
"""Set local roles for a user."""
def manage_delLocalRoles(userids, REQUEST=None):
"""Remove all local roles for a user."""
#------------------------------------------------------------
def access_debug_info():
"""Return debug info"""
def valid_roles():
"""Return list of valid roles"""
def validate_roles(roles):
"""Return true if all given roles are valid"""
def userdefined_roles():
"""Return list of user-defined roles"""
def manage_defined_roles(submit=None, REQUEST=None):
"""Called by management screen."""
def _addRole(role, REQUEST=None):
""" """
def _delRoles(roles, REQUEST=None):
""" """
def _has_user_defined_role(role):
""" """
def manage_editRoles(REQUEST, acl_type='A', acl_roles=[]):
""" """
def _setRoles(acl_type, acl_roles):
""" """
def possible_permissions():
""" """
class ISimpleItem(IItem, IPersistent, IAcquisition, IRoleManager):
"""Not-so-simple item"""
class ICopyContainer(Interface):
"""Interface for containerish objects which allow cut/copy/paste"""
# The following three methods should be overridden to store sub-objects
# as non-attributes.
def _setOb(id, object):
""" """
def _delOb(id):
""" """
def _getOb(id, default=None):
""" """
def manage_CopyContainerFirstItem(self, REQUEST):
""" """
def manage_CopyContainerAllItems(self, REQUEST):
return map(lambda i, s=self: s._getOb(i), tuple(REQUEST['ids']))
def manage_cutObjects(ids=None, REQUEST=None):
"""Put a reference to the objects named in ids in the clip board"""
def manage_copyObjects(ids=None, REQUEST=None, RESPONSE=None):
"""Put a reference to the objects named in ids in the clip board"""
def _get_id(id):
"""Allow containers to override the generation of object copy id by
attempting to call its _get_id method, if it exists."""
def manage_pasteObjects(cb_copy_data=None, REQUEST=None):
"""Paste previously copied objects into the current object. If
calling manage_pasteObjects from python code, pass the result
of a previous call to manage_cutObjects or manage_copyObjects
as the first argument."""
manage_renameForm = Attribute("""Rename management view""")
def manage_renameObjects(ids=[], new_ids=[], REQUEST=None):
"""Rename several sub-objects"""
def manage_renameObject(id, new_id, REQUEST=None):
"""Rename a particular sub-object"""
def manage_clone(ob, id, REQUEST=None):
"""Clone an object, creating a new object with the given id."""
def cb_dataValid():
"""Return true if clipboard data seems valid."""
def cb_dataItems():
"""List of objects in the clip board"""
def _verifyObjectPaste(object, validate_src=1):
"""Verify whether the current user is allowed to paste the passed
object into self. This is determined by checking to see if the
user could create a new object of the same meta_type of the
object passed in and checking that the user actually is
allowed to access the passed in object in its existing
context.
Passing a false value for the validate_src argument will skip
checking the passed in object in its existing context. This is
mainly useful for situations where the passed in object has no
existing context, such as checking an object during an import
(the object will not yet have been connected to the
acquisition hierarchy)."""
class INavigation(Interface):
"""Basic navigation UI support"""
manage = Attribute(""" """)
manage_menu = Attribute(""" """)
manage_top_frame = Attribute(""" """)
manage_page_header = Attribute(""" """)
manage_page_footer = Attribute(""" """)
manage_form_title = Attribute("""Add Form""")
zope_quick_start = Attribute(""" """)
manage_copyright = Attribute(""" """)
manage_zmi_prefs = Attribute(""" """)
def manage_zmi_logout(REQUEST, RESPONSE):
"""Logout current user"""
INavigation.setTaggedValue('manage_page_style.css', Attribute(""" """))
class IDAVCollection(IDAVResource):
"""The Collection class provides basic WebDAV support for collection
objects. It provides default implementations for all supported
WebDAV HTTP methods. The behaviors of some WebDAV HTTP methods for
collections are slightly different than those for non-collection
resources."""
__dav_collection__ = Bool(
title=u"Is a DAV collection",
description=u"Should be true",
)
def dav__init(request, response):
""" """
def HEAD(REQUEST, RESPONSE):
"""Retrieve resource information without a response body."""
def PUT(REQUEST, RESPONSE):
"""The PUT method has no inherent meaning for collection
resources, though collections are not specifically forbidden
to handle PUT requests. The default response to a PUT request
for collections is 405 (Method Not Allowed)."""
def DELETE(REQUEST, RESPONSE):
"""Delete a collection resource. For collection resources, DELETE
may return either 200 (OK) or 204 (No Content) to indicate total
success, or may return 207 (Multistatus) to indicate partial
success. Note that in Zope a DELETE currently never returns 207."""
def listDAVObjects():
""" """
class IObjectManager(IZopeObject, ICopyContainer, INavigation, IManageable,
IAcquisition, IPersistent, IDAVCollection, ITraversable):
"""Generic object manager
This interface provides core behavior for collections of
heterogeneous objects."""
meta_types = Tuple(
title=u"Meta types",
description=u"Sub-object types that are specific to this object",
)
isAnObjectManager = Bool(
title=u"Is an object manager",
)
manage_main = Attribute(""" """)
manage_index_main = Attribute(""" """)
manage_addProduct = Attribute(""" """)
manage_importExportForm = Attribute(""" """)
def all_meta_types(interfaces=None):
""" """
def filtered_meta_types(user=None):
"""Return a list of the types for which the user has adequate
permission to add that type of object."""
def _setOb(id, object):
""" """
def _delOb(id):
""" """
def _getOb(id, default=None):
""" """
def _setObject(id, object, roles=None, user=None, set_owner=1):
""" """
def _delObject(id, dp=1):
""" """
def objectIds(spec=None):
"""Returns a list of subobject ids of the current object. If 'spec'
is specified, returns objects whose meta_type matches 'spec'.
"""
def objectValues(spec=None):
"""Returns a list of actual subobjects of the current object. If
'spec' is specified, returns only objects whose meta_type
match 'spec'."""
def objectItems(spec=None):
"""Returns a list of (id, subobject) tuples of the current object. If
'spec' is specified, returns only objects whose meta_type
match 'spec'"""
def objectMap():
"""Return a tuple of mappings containing subobject meta-data"""
def superValues(t):
"""Return all of the objects of a given type located in this object
and containing objects."""
def manage_delObjects(ids=[], REQUEST=None):
"""Delete a subordinate object
The objects specified in 'ids' get deleted.
"""
def tpValues():
"""Return a list of subobjects, used by tree tag."""
def manage_exportObject(id='', download=None, toxml=None,
RESPONSE=None,REQUEST=None):
"""Exports an object to a file and returns that file."""
def manage_importObject(file, REQUEST=None, set_owner=1):
"""Import an object from a file"""
def _importObjectFromFile(filepath, verify=1, set_owner=1):
""" """
def __getitem__(key):
""" """
class IPropertyManager(Interface):
"""The PropertyManager mixin class provides an object with
transparent property management. An object which wants to
have properties should inherit from PropertyManager.
An object may specify that it has one or more predefined
properties, by specifying an _properties structure in its
class::
_properties=({'id':'title', 'type': 'string', 'mode': 'w'},
{'id':'color', 'type': 'string', 'mode': 'w'},
)
The _properties structure is a sequence of dictionaries, where
each dictionary represents a predefined property. Note that if a
predefined property is defined in the _properties structure, you
must provide an attribute with that name in your class or instance
that contains the default value of the predefined property.
Each entry in the _properties structure must have at least an 'id'
and a 'type' key. The 'id' key contains the name of the property,
and the 'type' key contains a string representing the object's type.
The 'type' string must be one of the values: 'float', 'int', 'long',
'string', 'lines', 'text', 'date', 'tokens', 'selection', or
'multiple section'.
For 'selection' and 'multiple selection' properties, there is an
addition item in the property dictionay, 'select_variable' which
provides the name of a property or method which returns a list of
strings from which the selection(s) can be chosen.
Each entry in the _properties structure may *optionally* provide a
'mode' key, which specifies the mutability of the property. The 'mode'
string, if present, must contain 0 or more characters from the set
'w','d'.
A 'w' present in the mode string indicates that the value of the
property may be changed by the user. A 'd' indicates that the user
can delete the property. An empty mode string indicates that the
property and its value may be shown in property listings, but that
it is read-only and may not be deleted.
Entries in the _properties structure which do not have a 'mode' key
are assumed to have the mode 'wd' (writeable and deleteable).
To fully support property management, including the system-provided
tabs and user interfaces for working with properties, an object which
inherits from PropertyManager should include the following entry in
its manage_options structure::
{'label':'Properties', 'action':'manage_propertiesForm',}
to ensure that a 'Properties' tab is displayed in its management
interface. Objects that inherit from PropertyManager should also
include the following entry in its __ac_permissions__ structure::
('Manage properties', ('manage_addProperty',
'manage_editProperties',
'manage_delProperties',
'manage_changeProperties',)),
"""
manage_propertiesForm = Attribute(""" """)
manage_propertyTypeForm = Attribute(""" """)
title = BytesLine(
title=u"Title"
)
_properties = Tuple(
title=u"Properties",
)
propertysheets = Attribute(""" """)
def valid_property_id(id):
""" """
def hasProperty(id):
"""Return true if object has a property 'id'"""
def getProperty(id, d=None):
"""Get the property 'id', returning the optional second
argument or None if no such property is found."""
def getPropertyType(id):
"""Get the type of property 'id', returning None if no
such property exists"""
def _setProperty(id, value, type='string'):
""" """
def _updateProperty(id, value):
"""Update the value of an existing property. If value is a string, an
attempt will be made to convert the value to the type of the
existing property."""
def _delProperty(id):
""" """
def propertyIds():
"""Return a list of property ids """
def propertyValues():
"""Return a list of actual property objects """
def propertyItems():
"""Return a list of (id,property) tuples """
def _propertyMap():
"""Return a tuple of mappings, giving meta-data for properties """
def propertyMap():
"""Return a tuple of mappings, giving meta-data for properties.
Return copies of the real definitions for security.
"""
def propertyLabel(id):
"""Return a label for the given property id
"""
def propdict():
""" """
# Web interface
def manage_addProperty(id, value, type, REQUEST=None):
"""Add a new property via the web. Sets a new property with
the given id, type, and value."""
def manage_editProperties(REQUEST):
"""Edit object properties via the web.
The purpose of this method is to change all property values,
even those not listed in REQUEST; otherwise checkboxes that
get turned off will be ignored. Use manage_changeProperties()
instead for most situations.
"""
def manage_changeProperties(REQUEST=None, **kw):
"""Change existing object properties.
Change object properties by passing either a mapping object
of name:value pairs {'foo':6} or passing name=value parameters
"""
def manage_changePropertyTypes(old_ids, props, REQUEST=None):
"""Replace one set of properties with another
Delete all properties that have ids in old_ids, then add a
property for each item in props. Each item has a new_id,
new_value, and new_type. The type of new_value should match
new_type.
"""
def manage_delProperties(ids=None, REQUEST=None):
"""Delete one or more properties specified by 'ids'."""
class IFindSupport(Interface):
"""Find support for Zope Folders"""
manage_findFrame = Attribute(""" """)
manage_findForm = Attribute(""" """)
manage_findAdv = Attribute(""" """)
manage_findResult = Attribute(""" """)
def ZopeFind(obj, obj_ids=None, obj_metatypes=None,
obj_searchterm=None, obj_expr=None,
obj_mtime=None, obj_mspec=None,
obj_permission=None, obj_roles=None,
search_sub=0,
REQUEST=None, result=None, pre=''):
"""Zope Find interface"""
PrincipiaFind = ZopeFind
def ZopeFindAndApply(obj, obj_ids=None, obj_metatypes=None,
obj_searchterm=None, obj_expr=None,
obj_mtime=None, obj_mspec=None,
obj_permission=None, obj_roles=None,
search_sub=0,
REQUEST=None, result=None, pre='',
apply_func=None, apply_path=''):
"""Zope Find interface and apply"""
class IFolder(IObjectManager, IPropertyManager, IRoleManager,
IDAVCollection, IItem, IFindSupport):
"""Folders are basic container objects that provide a standard
interface for object management. Folder objects also implement a
management interface and can have arbitrary properties."""
class IOrderedContainer(Interface):
""" Ordered Container interface.
This interface provides a common mechanism for maintaining ordered
collections.
"""
def moveObjectsByDelta(ids, delta, subset_ids=None):
""" Move specified sub-objects by delta.
If delta is higher than the possible maximum, objects will be moved to
the bottom. If delta is lower than the possible minimum, objects will
be moved to the top.
If subset_ids is not None, delta will be interpreted relative to the
subset specified by a sequence of ids. The position of objects that
are not part of this subset will not be changed.
The order of the objects specified by ids will always be preserved. So
if you don't want to change their original order, make sure the order
of ids corresponds to their original order.
If an object with id doesn't exist an error will be raised.
Permission -- Manage properties
Returns -- Number of moved sub-objects
"""
def moveObjectsUp(ids, delta=1, subset_ids=None):
""" Move specified sub-objects up by delta in container.
If no delta is specified, delta is 1. See moveObjectsByDelta for more
details.
Permission -- Manage properties
Returns -- Number of moved sub-objects
"""
def moveObjectsDown(ids, delta=1, subset_ids=None):
""" Move specified sub-objects down by delta in container.
If no delta is specified, delta is 1. See moveObjectsByDelta for more
details.
Permission -- Manage properties
Returns -- Number of moved sub-objects
"""
def moveObjectsToTop(ids, subset_ids=None):
""" Move specified sub-objects to top of container.
See moveObjectsByDelta for more details.
Permission -- Manage properties
Returns -- Number of moved sub-objects
"""
def moveObjectsToBottom(ids, subset_ids=None):
""" Move specified sub-objects to bottom of container.
See moveObjectsByDelta for more details.
Permission -- Manage properties
Returns -- Number of moved sub-objects
"""
def orderObjects(key, reverse=None):
""" Order sub-objects by key and direction.
Permission -- Manage properties
Returns -- Number of moved sub-objects
"""
def getObjectPosition(id):
""" Get the position of an object by its id.
Permission -- Access contents information
Returns -- Position
"""
def moveObjectToPosition(id, position):
""" Move specified object to absolute position.
Permission -- Manage properties
Returns -- Number of moved sub-objects
"""
class IOrderedFolder(IOrderedContainer, IFolder):
"""Ordered folder"""
class IApplication(IFolder, IFindSupport):
"""Top-level system object"""
isTopLevelPrincipiaApplicationObject = Bool(
title=u"Is top level Principa application object",
)
HelpSys = Attribute("Help system")
p_ = Attribute(""" """)
misc_ = Attribute("Misc.")
def PrincipiaRedirect(destination,URL1):
"""Utility function to allow user-controlled redirects"""
Redirect = ZopeRedirect = PrincipiaRedirect
def __bobo_traverse__(REQUEST, name=None):
"""Bobo traverse"""
def PrincipiaTime(*args):
"""Utility function to return current date/time"""
ZopeTime = PrincipiaTime
def ZopeAttributionButton():
"""Returns an HTML fragment that displays the 'powered by zope'
button along with a link to the Zope site."""
test_url = ZopeAttributionButton
def absolute_url(relative=0):
'''The absolute URL of the root object is BASE1 or "/".'''
def absolute_url_path():
'''The absolute URL path of the root object is BASEPATH1 or "/".'''
def virtual_url_path():
'''The virtual URL path of the root object is empty.'''
def getPhysicalPath():
'''Returns a path that can be used to access this object again
later, for example in a copy/paste operation. Designed to
be used with getPhysicalRoot().
'''
def getPhysicalRoot():
"""Returns self"""
def fixupZClassDependencies(rebuild=0):
""" """
def checkGlobalRegistry():
"""Check the global (zclass) registry for problems, which can
be caused by things like disk-based products being deleted.
Return true if a problem is found"""
class IMenuItemType(IInterface):
"""Menu item type
Menu item types are interfaces that define classes of
menu items.
"""
\ No newline at end of file
<configure xmlns="http://namespaces.zope.org/five">
<!-- IPersistent, IPersistentExtra -->
<!-- Acquisition -->
<implements
class="Acquisition.ImplicitAcquisitionWrapper"
interface=".interfaces.IAcquisitionWrapper"
/>
<implements
class="Acquisition.ExplicitAcquisitionWrapper"
interface=".interfaces.IAcquisitionWrapper"
/>
<implements
class="Acquisition.Implicit"
interface=".interfaces.IAcquisition"
/>
<implements
class="Acquisition.Explicit"
interface=".interfaces.IAcquisition"
/>
<!-- DAV -->
<implements
class="webdav.Lockable.LockableItem"
interface=".interfaces.IWriteLock"
/>
<implements
class="webdav.Resource.Resource"
interface=".interfaces.IDAVResource"
/>
<implements
class="webdav.Collection.Collection"
interface=".interfaces.IDAVCollection"
/>
<!-- OFS -->
<implements
class="OFS.CopySupport.CopySource"
interface=".interfaces.ICopySource"
/>
<implements
class="OFS.CopySupport.CopyContainer"
interface=".interfaces.ICopyContainer"
/>
<implements
class="OFS.Traversable.Traversable"
interface=".interfaces.ITraversable"
/>
<implements
class="OFS.SimpleItem.Item"
interface=".interfaces.IItem"
/>
<implements
class="OFS.SimpleItem.Item_w__name__"
interface=".interfaces.IItemWithName"
/>
<implements
class="OFS.SimpleItem.SimpleItem"
interface=".interfaces.ISimpleItem"
/>
<implements
class="OFS.ObjectManager.ObjectManager"
interface=".interfaces.IObjectManager"
/>
<implements
class="OFS.PropertyManager.PropertyManager"
interface=".interfaces.IPropertyManager"
/>
<implements
class="OFS.FindSupport.FindSupport"
interface=".interfaces.IFindSupport"
/>
<implements
class="OFS.Folder.Folder"
interface=".interfaces.IFolder"
/>
<implements
class="OFS.OrderSupport.OrderSupport"
interface=".interfaces.IOrderedContainer"
/>
<implements
class="OFS.OrderedFolder.OrderedFolder"
interface=".interfaces.IOrderedFolder"
/>
<implements
class="OFS.Application.Application"
interface=".interfaces.IApplication"
/>
<!-- App -->
<implements
class="App.Undo.UndoSupport"
interface=".interfaces.IUndoSupport"
/>
<implements
class="App.Management.Navigation"
interface=".interfaces.INavigation"
/>
<!-- AccessControl -->
<implements
class="AccessControl.Owned.Owned"
interface=".interfaces.IOwned"
/>
<implements
class="AccessControl.PermissionMapping.RoleManager"
interface=".interfaces.IPermissionMapping"
/>
<implements
class="AccessControl.Role.RoleManager"
interface=".interfaces.IRoleManager"
/>
</configure>
NAME=Five
PYTHON="/usr/bin/python"
TMPDIR=~/tmp
CURDIR=~/src/projects/Five
BASE_DIR=${CURDIR}/..
SOFTWARE_HOME=~/src/zope/2_7/lib/python
INSTANCE_HOME=~/src/instance/five
ZOPE_CONFIG=~/src/instance/five/etc/zope.conf
Z3=~/src/z3/five
.PHONY : clean test reindent reindent_clean
.PHONY : default
# default: The default step (invoked when make is called without a target)
default: clean test
clean :
find . \( -name '*~' -o -name '*.py[co]' -o -name '*.bak' -o -name '\.#*' \) -exec rm {} \; -print
reindent :
~/src/reindent.py -r -v .
test :
export INSTANCE_HOME=${INSTANCE_HOME}; \
export SOFTWARE_HOME=${SOFTWARE_HOME}; \
export PYTHONPATH=${Z3}; \
export ZOPE_CONFIG=${ZOPE_CONFIG}; \
cd ${CURDIR}/tests && ${PYTHON} -O runalltests.py
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:meta="http://namespaces.zope.org/meta">
<meta:directives namespace="http://namespaces.zope.org/zope">
<meta:directive
name="permission"
schema="zope.app.security.metadirectives.IDefinePermissionDirective"
handler="zope.app.security.metaconfigure.definePermission"
/>
<meta:directive
name="redefinePermission"
schema="zope.app.security.metadirectives.IRedefinePermission"
handler="zope.app.security.metaconfigure.redefinePermission"
/>
<meta:directive
name="interface"
schema="zope.app.component.metadirectives.IInterfaceDirective"
handler="zope.app.component.metaconfigure.interface"
/>
<meta:directive
name="view"
schema="zope.app.component.metadirectives.IViewDirective"
handler="zope.app.component.metaconfigure.view"
/>
<meta:directive
name="adapter"
schema="zope.app.component.metadirectives.IAdapterDirective"
handler="zope.app.component.metaconfigure.adapter"
/>
<meta:directive
name="subscriber"
schema="zope.app.component.metadirectives.ISubscriberDirective"
handler="zope.app.component.metaconfigure.subscriber"
/>
<meta:directive
name="utility"
schema="zope.app.component.metadirectives.IUtilityDirective"
handler="zope.app.component.metaconfigure.utility"
/>
<meta:directive
name="serviceType"
schema="zope.app.component.metadirectives.IServiceTypeDirective"
handler="zope.app.component.metaconfigure.serviceType"
/>
<meta:directive
name="service"
schema="zope.app.component.metadirectives.IServiceDirective"
handler="zope.app.component.metaconfigure.service"
/>
<meta:complexDirective
name="content"
schema="zope.app.component.metadirectives.IClassDirective"
handler=".metaconfigure.ContentDirective"
>
<meta:subdirective
name="implements"
schema="zope.app.component.metadirectives.IImplementsSubdirective"
/>
<meta:subdirective
name="require"
schema="zope.app.component.metadirectives.IRequireSubdirective"
/>
<meta:subdirective
name="allow"
schema="zope.app.component.metadirectives.IAllowSubdirective"
/>
</meta:complexDirective>
</meta:directives>
<meta:directives namespace="http://namespaces.zope.org/browser">
<meta:directive
name="layer"
schema="zope.app.publisher.browser.metadirectives.ILayerDirective"
handler="zope.app.publisher.browser.metaconfigure.layer"
/>
<meta:directive
name="skin"
schema="zope.app.publisher.browser.metadirectives.ISkinDirective"
handler="zope.app.publisher.browser.metaconfigure.skin"
/>
<meta:directive
name="defaultSkin"
schema="zope.app.publisher.browser.metadirectives.IDefaultSkinDirective"
handler="zope.app.publisher.browser.metaconfigure.defaultSkin"
/>
<meta:directive
name="defaultView"
schema="zope.app.publisher.browser.metadirectives.IDefaultViewDirective"
handler=".browserconfigure.defaultView"
/>
<meta:directive
name="page"
schema="zope.app.publisher.browser.metadirectives.IPageDirective"
handler=".browserconfigure.page"
/>
<meta:complexDirective
name="pages"
schema="zope.app.publisher.browser.metadirectives.IPagesDirective"
handler=".browserconfigure.pages"
>
<meta:subdirective
name="page"
schema="zope.app.publisher.browser.metadirectives.IPagesPageSubdirective"
/>
</meta:complexDirective>
<meta:directive
name="resource"
schema="zope.app.publisher.browser.metadirectives.IResourceDirective"
handler=".browserconfigure.resource"
/>
<meta:directive
name="resourceDirectory"
schema="zope.app.publisher.browser.metadirectives.IResourceDirectoryDirective"
handler=".browserconfigure.resourceDirectory"
/>
<meta:directive
name="menu"
schema="zope.app.publisher.browser.metadirectives.IMenuDirective"
handler="zope.app.publisher.browser.globalbrowsermenuservice.menuDirective"
/>
<meta:directive
name="menuItem"
schema="zope.app.publisher.browser.metadirectives.IMenuItemDirective"
handler="zope.app.publisher.browser.globalbrowsermenuservice.menuItemDirective"
/>
<meta:complexDirective
name="menuItems"
schema="zope.app.publisher.browser.metadirectives.IMenuItemsDirective"
handler="zope.app.publisher.browser.globalbrowsermenuservice.menuItemsDirective"
>
<meta:subdirective
name="menuItem"
schema="zope.app.publisher.browser.metadirectives.IMenuItemSubdirective"
/>
</meta:complexDirective>
<meta:complexDirective
name="view"
schema="zope.app.publisher.browser.metadirectives.IViewDirective"
handler=".browserconfigure.view"
>
<meta:subdirective
name="page"
schema="zope.app.publisher.browser.metadirectives.IViewPageSubdirective"
/>
<meta:subdirective
name="defaultPage"
schema="zope.app.publisher.browser.metadirectives.IViewDefaultPageSubdirective"
/>
</meta:complexDirective>
<meta:complexDirective
name="editform"
schema="zope.app.form.browser.metadirectives.IEditFormDirective"
handler=".browserconfigure.EditFormDirective"
>
<meta:subdirective
name="widget"
schema="zope.app.form.browser.metadirectives.IWidgetSubdirective"
/>
</meta:complexDirective>
<meta:complexDirective
name="addform"
schema="zope.app.form.browser.metadirectives.IAddFormDirective"
handler=".browserconfigure.AddFormDirective"
>
<meta:subdirective
name="widget"
schema="zope.app.form.browser.metadirectives.IWidgetSubdirective"
/>
</meta:complexDirective>
</meta:directives>
<meta:directives namespace="http://namespaces.zope.org/five">
<!-- specific to Five -->
<meta:directive
name="loadProducts"
schema="zope.interface.Interface"
handler=".fiveconfigure.loadProducts"
/>
<meta:directive
name="loadProductsOverrides"
schema="zope.interface.Interface"
handler=".fiveconfigure.loadProductsOverrides"
/>
<meta:directive
name="implements"
schema=".fivedirectives.IImplementsDirective"
handler=".fiveconfigure.implements"
/>
<meta:directive
name="defaultViewable"
schema=".fivedirectives.IDefaultViewableDirective"
handler=".fiveconfigure.defaultViewable"
/>
<meta:directive
name="traversable"
schema=".fivedirectives.ITraversableDirective"
handler=".fiveconfigure.traversable"
/>
<meta:directive
name="sendEvents"
schema=".fivedirectives.ISendEventsDirective"
handler=".eventconfigure.sendEvents"
/>
<meta:directive
name="pagesFromDirectory"
schema=".fivedirectives.IPagesFromDirectoryDirective"
handler=".fiveconfigure.pagesFromDirectory"
/>
<!-- viewable is deprecated, use traversable instead -->
<meta:directive
name="viewable"
schema=".fivedirectives.ITraversableDirective"
handler=".fiveconfigure.viewable"
/>
<meta:directive
name="bridge"
schema=".fivedirectives.IBridgeDirective"
handler=".fiveconfigure.bridge"
/>
</meta:directives>
</configure>
# metaclass taken from PEAK.
# Martijn doesn't pretend to understand this.
from weakref import WeakValueDictionary
from types import ClassType
def makeClass(name,bases,dict):
"""makeClass(name, bases, dict) - enhanced class creation
"""
# Create either a "classic" Python class, an ExtensionClass, or a new-style
# class with autogenerated metaclass, based on the nature of the base
# classes involved
name = str(name) # De-unicode
metaclasses = [getattr(b,'__class__',type(b)) for b in bases]
if dict.has_key('__metaclass__'):
metaclasses.insert(0,dict['__metaclass__'])
if dict.has_key('__metaclasses__'):
metaclasses[0:0] = list(dict['__metaclasses__'])
metaclasses = normalizeBases(metaclasses)
if metaclasses:
# If we have metaclasses, it's not a classic class, so derive a
# single metaclass, and ask it to create the class.
if len(metaclasses)==1:
metaclass = metaclasses[0]
else:
metaclass = derivedMeta(metaclasses)
return metaclass(name,bases,dict)
# No metaclasses, it's a classic class, so use 'new.classobj'
from new import classobj; return classobj(name,bases,dict)
def normalizeBases(allBases):
return minimalBases([b for b in allBases if b is not makeClass])
def minimalBases(classes):
"""Reduce a list of base classes to its ordered minimum equivalent"""
classes = [c for c in classes if c is not ClassType]
candidates = []
for m in classes:
for n in classes:
if issubclass(n,m) and m is not n:
break
else:
# m has no subclasses in 'classes'
if m in candidates:
candidates.remove(m) # ensure that we're later in the list
candidates.append(m)
return candidates
metaReg = WeakValueDictionary()
def derivedMeta(metaclasses):
metaclasses = tuple(metaclasses)
derived = metaReg.get(metaclasses)
if derived is None:
normalized = tuple(normalizeBases(metaclasses))
derived = metaReg.get(normalized)
if derived is None:
if len(normalized)==1:
derived = normalized[0]
else:
derived = metaFromBases(normalized)(
'_'.join([n.__name__ for n in normalized]),
metaclasses, {}
)
try: metaReg[normalized] = derived
except TypeError: pass # Some metatypes can't be weakref'd
try: metaReg[metaclasses] = derived
except TypeError: pass
return derived
def metaFromBases(bases):
meta = tuple([getattr(b,'__class__',type(b)) for b in bases])
if meta==bases: raise TypeError("Incompatible root metatypes",bases)
return derivedMeta(meta)
##############################################################################
#
# Copyright (c) 2004 Five Contributors. All rights reserved.
#
# This software is distributed under the terms of the Zope Public
# License (ZPL) v2.1. See COPYING.txt for more information.
#
##############################################################################
"""Generic Components ZCML Handlers
$Id: metaconfigure.py 5287 2004-06-25 11:42:27Z philikon $
"""
from types import ModuleType
from zope.interface import classImplements
from zope.configuration.exceptions import ConfigurationError
from security import CheckerPublic
from security import protectName, initializeClass
class ContentDirective:
def __init__(self, _context, class_):
self.__class = class_
if isinstance(self.__class, ModuleType):
raise ConfigurationError('Content class attribute must be a class')
self.__context = _context
def implements(self, _context, interface):
for interface in interface:
_context.action(
discriminator = (
'five::directive:content', self.__class, object()),
callable = classImplements,
args = (self.__class, interface),
)
interface(_context, interface)
def require(self, _context, permission=None,
attributes=None, interface=None):
"""Require a the permission to access a specific aspect"""
if not (interface or attributes):
raise ConfigurationError("Nothing required")
if interface:
for i in interface:
if i:
self.__protectByInterface(i, permission)
if attributes:
self.__protectNames(attributes, permission)
def allow(self, _context, attributes=None, interface=None):
"""Like require, but with permission_id zope.Public"""
return self.require(_context, CheckerPublic, attributes, interface)
def __protectByInterface(self, interface, permission_id):
"Set a permission on names in an interface."
for n, d in interface.namesAndDescriptions(1):
self.__protectName(n, permission_id)
interface(self.__context, interface)
def __protectName(self, name, permission_id):
"Set a permission on a particular name."
self.__context.action(
discriminator = ('five:protectName', self.__class, name),
callable = protectName,
args = (self.__class, name, permission_id)
)
def __protectNames(self, names, permission_id):
"Set a permission on a bunch of names."
for name in names:
self.__protectName(name, permission_id)
def __call__(self):
"Handle empty/simple declaration."
return self.__context.action(
discriminator = ('five:initialize:class', self.__class),
callable = initializeClass,
args = (self.__class,)
)
'''A 'PageTemplateFile' without security restrictions.'''
import os, sys
# Zope 2
from Globals import package_home
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
# Zope 3
from zope.app.pagetemplate.viewpagetemplatefile import ViewMapper
from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
# Five
from ReuseUtils import rebindFunction
from TrustedExpression import getEngine, ModuleImporter
class ZopeTwoPageTemplateFile(PageTemplateFile):
"""A strange hybrid between Zope 2 and Zope 3 page template.
Uses Zope 2's engine, but with security disabled and with some
initialization and API from Zope 3.
"""
def __init__(self, filename, _prefix=None, content_type=None):
# XXX doesn't use content_type yet
self.ZBindings_edit(self._default_bindings)
path = self.get_path_from_prefix(_prefix)
self.filename = os.path.join(path, filename)
if not os.path.isfile(self.filename):
raise ValueError("No such file", self.filename)
basepath, ext = os.path.splitext(self.filename)
self.__name__ = os.path.basename(basepath)
def get_path_from_prefix(self, _prefix):
if isinstance(_prefix, str):
path = _prefix
else:
if _prefix is None:
_prefix = sys._getframe(2).f_globals
path = package_home(_prefix)
return path
_cook = rebindFunction(PageTemplateFile._cook,
getEngine=getEngine)
pt_render = rebindFunction(PageTemplateFile.pt_render,
getEngine=getEngine)
def _pt_getContext(self):
view = self._getContext()
try:
root = self.getPhysicalRoot()
here = view.context
except AttributeError:
# self has no attribute getPhysicalRoot.
# This typically happens when the template has
# no proper acquisition context. That means it has no view,
# since that's the normal context for a template in Five. /regebro
root = self.context.getPhysicalRoot()
here = self.context
view = None
request = getattr(root, 'REQUEST', None)
c = {'template': self,
'here': here,
'context': here,
'container': self._getContainer(),
'nothing': None,
'options': {},
'root': root,
'request': request,
'modules': ModuleImporter,
}
if view:
c['view'] = view
c['views'] = ViewMapper(here, request)
return c
pt_getContext = rebindFunction(_pt_getContext,
SecureModuleImporter=ModuleImporter)
# this is not in use right now, but would be how to integrate Zope 3 page
# templates instead of Zope 2 page templates
class FivePageTemplateFile(ViewPageTemplateFile):
def pt_getContext(self, instance, request, **_kw):
# instance is a View component
namespace = super(FivePageTemplateFile, self).pt_getContext(
instance, request, **_kw)
namespace['here'] = namespace['context']
return namespace
<configure xmlns="http://namespaces.zope.org/zope"
i18n_domain="Five">
<!-- Give common Zope2 and CMF permissions a permission ID
The title of the permission is what Zope 2 knows it under -->
<permission
id="zope2.Public"
title="Public, everyone can access"
/>
<permission
id="zope2.Private"
title="Private, only accessible from trusted code"
/>
<permission
id="zope2.AccessContentsInformation"
title="Access contents information"
/>
<permission
id="zope2.ChangeImagesFiles"
title="Change Images and Files"
/>
<permission
id="zope2.ChangeConfig"
title="Change configuration"
/>
<permission
id="zope2.ChangePermissions"
title="Change permissions"
/>
<permission
id="zope2.CopyOrMove"
title="Copy or Move"
/>
<permission
id="zope2.DefinePermissions"
title="Define permissions"
/>
<permission
id="zope2.DeleteObjects"
title="Delete objects"
/>
<permission
id="zope2.FTPAccess"
title="FTP access"
/>
<permission
id="zope2.ImportExport"
title="Import/Export objects"
/>
<permission
id="zope2.ManageProperties"
title="Manage properties"
/>
<permission
id="zope2.ManageUsers"
title="Manage users"
/>
<permission
id="zope2.Undo"
title="Undo changes"
/>
<permission
id="zope2.View"
title="View"
/>
<permission
id="zope2.ViewHistory"
title="View History"
/>
<permission
id="zope2.ViewManagementScreens"
title="View management screens"
/>
<permission
id="zope2.WebDAVLock"
title="WebDAV Lock items"
/>
<permission
id="zope2.WebDAVUnlock"
title="WebDAV Unlock items"
/>
<permission
id="zope2.WebDAVAccess"
title="WebDAV access"
/>
<!-- CMF Core Permissions -->
<permission
id="cmf.ListFolderContents"
title="List folder contents"
/>
<permission
id="cmf.ListUndoableChanges"
title="List undoable changes"
/>
<permission
id="cmf.AccessInactivePortalContent"
title="Access inactive portal content"
/>
<permission
id="cmf.ManagePortal"
title="Manage portal"
/>
<permission
id="cmf.ModifyPortalContent"
title="Modify portal content"
/>
<permission
id="cmf.ManageProperties"
title="Manage properties"
/>
<permission
id="cmf.ListPortalMembers"
title="List portal members"
/>
<permission
id="cmf.AddPortalFolders"
title="Add portal folders"
/>
<permission
id="cmf.AddPortalContent"
title="Add portal content"
/>
<permission
id="cmf.AddPortalMember"
title="Add portal member"
/>
<permission
id="cmf.SetOwnPassword"
title="Set own password"
/>
<permission
id="cmf.SetOwnProperties"
title="Set own properties"
/>
<permission
id="cmf.MailForgottonPassword"
title="Mail forgotten password"
/>
<permission
id="cmf.RequestReview"
title="Request review"
/>
<permission
id="cmf.ReviewPortalContent"
title="Review portal content"
/>
<permission
id="cmf.AccessFuturePortalContent"
title="Access future portal content"
/>
</configure>
##############################################################################
#
# Copyright (c) 2004 Five Contributors. All rights reserved.
#
# This software is distributed under the terms of the Zope Public
# License (ZPL) v2.1. See COPYING.txt for more information.
#
##############################################################################
"""Provide basic resource functionality
$Id: browser.py 5259 2004-06-23 15:59:52Z philikon $
"""
import os
import urllib
from Acquisition import Explicit, aq_inner, aq_parent
from ComputedAttribute import ComputedAttribute
from browser import BrowserView
from OFS.Traversable import Traversable as OFSTraversable
from zope.exceptions import NotFoundError
from zope.interface import implements
from zope.component.interfaces import IResource
from zope.component import getViewProviding
from zope.publisher.interfaces.browser import IBrowserPublisher
from zope.app.traversing.browser.interfaces import IAbsoluteURL
from zope.app.datetimeutils import time as timeFromDateTimeString
from zope.app.publisher.fileresource import File, Image
from zope.app.publisher.pagetemplateresource import PageTemplate
from zope.app.publisher.browser.resources import empty
_marker = []
class Resource(Explicit):
"""A publishable resource
"""
implements(IResource)
def __init__(self, request):
self.request = request
def __call__(self):
name = self.__name__
container = self.__parent__
url = str(getViewProviding(container, IAbsoluteURL, self.request))
url = urllib.unquote(url)
if not isinstance(container, DirectoryResource):
name = '++resource++%s' % name
return "%s/%s" % (url, name)
class PageTemplateResource(BrowserView, Resource):
#implements(IBrowserPublisher)
def __browser_default__(self, request):
return self, ()
def __call__(self):
pt = self.context
return pt(self.request)
class FileResource(BrowserView, Resource):
"""A publishable file-based resource"""
#implements(IBrowserPublisher)
def __browser_default__(self, request):
return self, (request.REQUEST_METHOD,)
def GET(self):
"""Default content"""
file = self.context
request = self.request
response = request.response
# HTTP If-Modified-Since header handling. This is duplicated
# from OFS.Image.Image - it really should be consolidated
# somewhere...
header = request.environ.get('If-Modified-Since', None)
if header is not None:
header = header.split(';')[0]
# Some proxies seem to send invalid date strings for this
# header. If the date string is not valid, we ignore it
# rather than raise an error to be generally consistent
# with common servers such as Apache (which can usually
# understand the screwy date string as a lucky side effect
# of the way they parse it).
try: mod_since=long(timeFromDateTimeString(header))
except: mod_since=None
if mod_since is not None:
if getattr(file, 'lmt', None):
last_mod = long(file.lmt)
else:
last_mod = long(0)
if last_mod > 0 and last_mod <= mod_since:
response.setStatus(304)
return ''
response.setHeader('Content-Type', file.content_type)
response.setHeader('Last-Modified', file.lmh)
# Cache for one day
response.setHeader('Cache-Control', 'public,max-age=86400')
f = open(file.path, 'rb')
data = f.read()
f.close()
return data
def HEAD(self):
file = self.context
response = self.request.response
response = self.request.response
response.setHeader('Content-Type', file.content_type)
response.setHeader('Last-Modified', file.lmh)
# Cache for one day
response.setHeader('Cache-Control', 'public,max-age=86400')
return ''
class ResourceFactory:
factory = None
resource = None
def __init__(self, name, path, resource_factory=None):
self.__name = name
self.__rsrc = self.factory(path, name)
if resource_factory is not None:
self.resource = resource_factory
def __call__(self, request):
resource = self.resource(self.__rsrc, request)
return resource
def _PageTemplate(self, path, name):
# PageTemplate doesn't take a name parameter,
# which makes it different from FileResource.
# This is probably an error.
template = PageTemplate(path)
template.__name__ = name
return template
class PageTemplateResourceFactory(ResourceFactory):
"""A factory for Page Template resources"""
factory = _PageTemplate
resource = PageTemplateResource
class FileResourceFactory(ResourceFactory):
"""A factory for File resources"""
factory = File
resource = FileResource
class ImageResourceFactory(ResourceFactory):
"""A factory for Image resources"""
factory = Image
resource = FileResource
# we only need this class a context for DirectoryResource
class Directory:
def __init__(self, path, name):
self.path = path
self.__name__ = name
class DirectoryResource(BrowserView, Resource, OFSTraversable):
#implements(IBrowserPublisher)
resource_factories = {
'gif': ImageResourceFactory,
'png': ImageResourceFactory,
'jpg': ImageResourceFactory,
'pt': PageTemplateResourceFactory,
'zpt': PageTemplateResourceFactory,
'html': PageTemplateResourceFactory,
}
default_factory = FileResourceFactory
def getId(self):
name = self.__name__
if not name.startswith('++resource++'):
name = '++resource++%s' % self.__name__
return name
def __browser_default__(self, request):
'''See interface IBrowserPublisher'''
return empty, ()
def __getitem__(self, name):
res = self.get(name, None)
if res is None:
raise KeyError, name
return res
def get(self, name, default=_marker):
path = self.context.path
filename = os.path.join(path, name)
if not os.path.isfile(filename):
if default is _marker:
raise NotFoundError(name)
return default
ext = name.split('.')[-1]
factory = self.resource_factories.get(ext, self.default_factory)
resource = factory(name, filename)(self.request)
resource.__name__ = name
resource.__parent__ = self
# XXX __of__ wrapping is usually done on traversal.
# However, we don't want to subclass Traversable (or do we?)
# The right thing should probably be a specific (and very simple)
# traverser that does __getitem__ and __of__.
return resource.__of__(self)
class DirectoryResourceFactory(ResourceFactory):
factory = Directory
resource = DirectoryResource
##############################################################################
#
# Copyright (c) 2004 Five Contributors. All rights reserved.
#
# This software is distributed under the terms of the Zope Public
# License (ZPL) v2.1. See COPYING.txt for more information.
#
##############################################################################
"""Five security handling
$Id: security.py 8281 2005-01-14 18:20:18Z regebro $
"""
from zope.interface import implements
from zope.component import queryUtility, getUtility
from zope.app.security.interfaces import IPermission
from zope.app import zapi
from AccessControl import ClassSecurityInfo, getSecurityManager
from Globals import InitializeClass
from types import StringTypes
CheckerPublicId = 'zope.Public'
CheckerPrivateId = 'zope2.Private'
from zope.security.checker import CheckerPublic
def getSecurityInfo(klass):
sec = {}
info = vars(klass)
if info.has_key('__ac_permissions__'):
sec['__ac_permissions__'] = info['__ac_permissions__']
for k, v in info.items():
if k.endswith('__roles__'):
sec[k] = v
return sec
def clearSecurityInfo(klass):
sec = {}
info = vars(klass)
if info.has_key('__ac_permissions__'):
delattr(klass, '__ac_permissions__')
for k, v in info.items():
if k.endswith('__roles__'):
delattr(klass, k)
def checkPermission(permission, object, interaction=None):
"""Return whether security policy allows permission on object.
Arguments:
permission -- A permission name
object -- The object being accessed according to the permission
interaction -- This Zope 3 concept has no equivalent in Zope 2,
and is ignored.
checkPermission is guaranteed to return True if permission is
CheckerPublic or None.
"""
if (permission in ('zope.Public', 'zope2.Public') or
permission is None or permission is CheckerPublic):
return True
if isinstance(permission, StringTypes):
permission = zapi.queryUtility(IPermission, unicode(permission))
if permission is None:
return False
if getSecurityManager().checkPermission(permission.title, object):
return True
return False
def initializeClass(klass):
InitializeClass(klass)
def _getSecurity(klass):
# a Zope 2 class can contain some attribute that is an instance
# of ClassSecurityInfo. Zope 2 scans through things looking for
# an attribute that has the name __security_info__ first
info = vars(klass)
for k, v in info.items():
if hasattr(v, '__security_info__'):
return v
# we stuff the name ourselves as __security__, not security, as this
# could theoretically lead to name clashes, and doesn't matter for
# zope 2 anyway.
security = ClassSecurityInfo()
setattr(klass, '__security__', security)
return security
def protectName(klass, name, permission_id):
"""Protect the attribute 'name' on 'klass' using the given
permission"""
security = _getSecurity(klass)
# XXX: Sometimes, the object CheckerPublic is used instead of the
# string zope.Public. I haven't ben able to figure out why, or if
# it is correct, or a bug. So this is a workaround.
if permission_id is CheckerPublic:
security.declarePublic(name)
return
# Zope 2 uses string, not unicode yet
name = str(name)
if permission_id == CheckerPublicId:
security.declarePublic(name)
elif permission_id == CheckerPrivateId:
security.declarePrivate(name)
else:
permission = getUtility(IPermission, name=permission_id)
# Zope 2 uses string, not unicode yet
perm = str(permission.title)
security.declareProtected(perm, name)
def protectClass(klass, permission_id):
"""Protect the whole class with the given permission"""
security = _getSecurity(klass)
if permission_id == CheckerPublicId:
security.declareObjectPublic()
elif permission_id == CheckerPrivateId:
security.declareObjectPrivate()
else:
permission = getUtility(IPermission, name=permission_id)
# Zope 2 uses string, not unicode yet
perm = str(permission.title)
security.declareObjectProtected(perm)
<configure xmlns="http://namespaces.zope.org/zope">
<serviceType
id="Utilities"
interface="zope.component.interfaces.IUtilityService" />
<service
serviceType="Utilities"
factory="zope.component.utility.GlobalUtilityService" />
<serviceType
id="Adapters"
interface="zope.component.interfaces.IAdapterService" />
<service
serviceType="Adapters"
factory="zope.component.adapter.GlobalAdapterService" />
<serviceType
id="Presentation"
interface="zope.component.interfaces.IPresentationService" />
<service
serviceType="Presentation"
factory="zope.component.presentation.GlobalPresentationService" />
</configure>
This directory contains Zope3-style instance configuration files:
* ``site.zcml`` is the root node of the instance's ZCML
configuration tree.
* ``package-includes`` may contain Zope3-style ZCML slugs to enable
3rd party packages that are not dropped into ``Products``.
Copy these files to your ``$INSTANCE_HOME/etc`` directory for
customization. If Five cannot find them there, it falls back to these
skeleton files.
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:five="http://namespaces.zope.org/five">
<!-- Copy this file to your ``INSTANCE_HOME/etc`` directory -->
<include package="Products.Five" />
<redefinePermission from="zope2.Public" to="zope.Public" />
<include files="package-includes/*-meta.zcml" />
<include files="package-includes/*-configure.zcml" />
<five:loadProducts />
<five:loadProductsOverrides />
</configure>
Five tests
==========
The tests need all products in the ``tests/products`` subdirectory
installed in your Zope instance's Products directory. On unixy
systems, this can be most simply done by a symlink::
cd myinstance/Products
ln -s Five/tests/products/FiveTest .
and so on for each product in tests/products. On other platforms, you
could manually copy these directories (though you'd need to do that
each time you change the tests).
The tests also require ZopeTestCase to be installed. ZopeTestCase can
be downloaded from here:
http://zope.org/Members/shh/ZopeTestCase
it needs to be installed in your Zope software's lib/python/Testing
directory.
Finally, if you have Zope 2.7.3 or better all you have to do is type::
./bin/zopectl test --dir Products/Five
to run the Five tests. For older versions of Zope you need to set the
following environment variables::
export INSTANCE_HOME=/path/to/instance
export SOFTWARE_HOME=/path/to/software/lib/python
Then you should be able to run the tests by typing::
python2.3 runalltests.py
If you have troubles running the tests because zope.conf is looked for
in lib/Testing/etc/zope.conf, then you are running a Zope version
older than Zope 2.7.2. Please upgrade to Zope 2.7.2.
from zope.interface import Interface, implements
from Products.Five import BrowserView
from AccessControl import ClassSecurityInfo
class IDummy(Interface):
"""Just a marker interface"""
class DummyView(BrowserView):
"""A dummy view"""
def foo(self):
"""A foo"""
return 'A foo view'
class Dummy1:
implements(IDummy)
def foo(self): pass
def bar(self): pass
def baz(self): pass
def keg(self): pass
def wot(self): pass
class Dummy2(Dummy1):
security = ClassSecurityInfo()
security.declarePublic('foo')
security.declareProtected('View management screens', 'bar')
security.declarePrivate('baz')
security.declareProtected('View management screens', 'keg')
# ZopeTestCase was not designed to run tests as part of the
# Zope test suite proper. In particular, it intercepts product
# installation. We have to work around this for the Five tests
# by starting up Zope2 before doing anything else.
#
# Note: fivetest must be imported first thing by test modules!
def _part_of_zope_suite():
# Find out if we run from softwarehome
from os.path import normcase, realpath
from App.config import getConfiguration
softwarehome = normcase(realpath(getConfiguration().softwarehome))
return normcase(realpath(__file__)).startswith(softwarehome)
def _start_zope():
# Startup Zope 2.7 or 2.8+
import Testing
try:
import Zope2 as Zope
except ImportError:
import Zope
Zope.startup()
def _load_test_config():
# Load up the ZCML config for the FiveTest product
from os.path import dirname, join
from Products.Five import zcml
from Products.Five.tests.products import FiveTest
prefix = dirname(FiveTest.__file__)
zcml.load_config(join(prefix, 'testing.zcml'), FiveTest)
def _main():
if _part_of_zope_suite():
_start_zope()
else:
from Testing.ZopeTestCase import installProduct
installProduct('Five')
installProduct('PythonScripts')
_load_test_config()
_main()
from Testing.ZopeTestCase import *
class FiveTestCase(ZopeTestCase):
pass
##############################################################################
#
# ZopeTestCase
#
# COPY THIS FILE TO YOUR 'tests' DIRECTORY.
#
# This version of framework.py will use the SOFTWARE_HOME
# environment variable to locate Zope and the Testing package.
#
# If the tests are run in an INSTANCE_HOME installation of Zope,
# Products.__path__ and sys.path with be adjusted to include the
# instance's Products and lib/python directories respectively.
#
# If you explicitly set INSTANCE_HOME prior to running the tests,
# auto-detection is disabled and the specified path will be used
# instead.
#
# If the 'tests' directory contains a custom_zodb.py file, INSTANCE_HOME
# will be adjusted to use it.
#
# If you set the ZEO_INSTANCE_HOME environment variable a ZEO setup
# is assumed, and you can attach to a running ZEO server (via the
# instance's custom_zodb.py).
#
##############################################################################
#
# The following code should be at the top of every test module:
#
# import os, sys
# if __name__ == '__main__':
# execfile(os.path.join(sys.path[0], 'framework.py'))
#
# ...and the following at the bottom:
#
# if __name__ == '__main__':
# framework()
#
##############################################################################
__version__ = '0.2.3'
# Save start state
#
__SOFTWARE_HOME = os.environ.get('SOFTWARE_HOME', '')
__INSTANCE_HOME = os.environ.get('INSTANCE_HOME', '')
if __SOFTWARE_HOME.endswith(os.sep):
__SOFTWARE_HOME = os.path.dirname(__SOFTWARE_HOME)
if __INSTANCE_HOME.endswith(os.sep):
__INSTANCE_HOME = os.path.dirname(__INSTANCE_HOME)
# Find and import the Testing package
#
if not sys.modules.has_key('Testing'):
p0 = sys.path[0]
if p0 and __name__ == '__main__':
os.chdir(p0)
p0 = ''
s = __SOFTWARE_HOME
p = d = s and s or os.getcwd()
while d:
if os.path.isdir(os.path.join(p, 'Testing')):
zope_home = os.path.dirname(os.path.dirname(p))
sys.path[:1] = [p0, p, zope_home]
break
p, d = s and ('','') or os.path.split(p)
else:
print 'Unable to locate Testing package.',
print 'You might need to set SOFTWARE_HOME.'
sys.exit(1)
# zope.conf must be read before 'import Testing'
import zopeconf
zopeconf.process()
import Testing, unittest
execfile(os.path.join(os.path.dirname(Testing.__file__), 'common.py'))
# Include ZopeTestCase support
#
if 1: # Create a new scope
p = os.path.join(os.path.dirname(Testing.__file__), 'ZopeTestCase')
if not os.path.isdir(p):
print 'Unable to locate ZopeTestCase package.',
print 'You might need to install ZopeTestCase.'
sys.exit(1)
ztc_common = 'ztc_common.py'
ztc_common_global = os.path.join(p, ztc_common)
f = 0
if os.path.exists(ztc_common_global):
execfile(ztc_common_global)
f = 1
if os.path.exists(ztc_common):
execfile(ztc_common)
f = 1
if not f:
print 'Unable to locate %s.' % ztc_common
sys.exit(1)
# Debug
#
print 'SOFTWARE_HOME: %s' % os.environ.get('SOFTWARE_HOME', 'Not set')
print 'INSTANCE_HOME: %s' % os.environ.get('INSTANCE_HOME', 'Not set')
sys.stdout.flush()
#import simplecontent
#import fancycontent
#def initialize(context):
#
# context.registerClass(
# simplecontent.SimpleContent,
# constructors = (simplecontent.manage_addSimpleContentForm,
# simplecontent.manage_addSimpleContent),
# )
#
# context.registerClass(
# simplecontent.CallableSimpleContent,
# constructors = (simplecontent.manage_addSimpleContentForm,
# simplecontent.manage_addCallableSimpleContent),
# )
#
# context.registerClass(
# simplecontent.IndexSimpleContent,
# constructors = (simplecontent.manage_addSimpleContentForm,
# simplecontent.manage_addIndexSimpleContent),
# )
#
# context.registerClass(
# fancycontent.FancyContent,
# constructors = (fancycontent.manage_addFancyContent,)
# )
#
# context.registerClass(
# simplecontent.FieldSimpleContent,
# constructors = (simplecontent.manage_addFieldSimpleContentForm,
# simplecontent.manage_addFieldSimpleContent,)
# )
<html metal:define-macro="birdmacro"><head><title>bird macro</title></head><body>Color: <metal:block define-slot="color" /><metal:block define-slot="birdinfo" /></body></html>
from Products.Five import BrowserView
from Products.Five import StandardMacros as BaseMacros
class SimpleContentView(BrowserView):
"""More docstring. Please Zope"""
def eagle(self):
"""Docstring"""
return "The eagle has landed"
def mouse(self):
"""Docstring"""
return "The mouse has been eaten by the eagle"
class FancyContentView(BrowserView):
"""Fancy, fancy stuff"""
def view(self):
return "Fancy, fancy"
class CallableNoDocstring:
def __call__(self):
return "No docstring"
def function_no_docstring(self):
return "No docstring"
class NoDocstringView(BrowserView):
def method(self):
return "No docstring"
function = function_no_docstring
object = CallableNoDocstring()
class NewStyleClass(object):
"""
This is a testclass to verify that new style classes are ignored
in browser:page
"""
def __init__(self, context, request):
"""Docstring"""
self.context = context
self.request = request
def method(self):
"""Docstring"""
return
class StandardMacros(BaseMacros):
macro_pages = ('bird_macros', 'dog_macros')
aliases = {'flying':'birdmacro',
'walking':'dogmacro'}
from zope.interface import implements
from interfaces import IAdaptable, IAdapted, IOrigin, IDestination
class Adaptable:
implements(IAdaptable)
def method(self):
return "The method"
class Adapter:
implements(IAdapted)
def __init__(self, context):
self.context = context
def adaptedMethod(self):
return "Adapted: %s" % self.context.method()
class Origin:
implements(IOrigin)
class OriginalAdapter:
implements(IDestination)
def __init__(self, context):
self.context = context
def method(self):
return "Original"
class OverrideAdapter:
implements(IDestination)
def __init__(self, context):
self.context = context
def method(self):
return "Overridden"
<p>Have you ever seen a cockatiel?</p>
<p tal:content="string:maybe">dunno</p>
<p tal:content="context/mymethod">Alpha</p>
<p tal:content="view/eagle">Beta</p>
<div tal:replace="structure views/flamingo.html">Gamma</div>
\ No newline at end of file
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser"
xmlns:five="http://namespaces.zope.org/five"
i18n_domain="fivetest">
<!-- this is a test whether five:traversable can be called more than
once on a class; SimpleContent inherits from api.Viewable, so
one directive suffices here -->
<five:traversable class=".simplecontent.SimpleContent" />
<five:defaultViewable class=".simplecontent.SimpleContent" />
<!-- this is a test whether the *directive* can be called -->
<!-- more than once without raising a conflicting -->
<!-- configuration exception -->
<five:traversable class=".simplecontent.SimpleContent" />
<five:defaultViewable class=".simplecontent.SimpleContent" />
<browser:defaultView
for=".interfaces.ISimpleContent"
name="eagle.txt"
/>
<!-- this tests whether five:traversable can be called on a class that
already provides __bobo_traverse__, such as our FancyContent -->
<five:traversable class=".fancycontent.FancyContent" />
<!-- this tests whether five:defaultViewable can be called on a class that
already provides __call__, such as our
CallableSimpleContent
-->
<five:defaultViewable class=".simplecontent.CallableSimpleContent" />
<!-- five:pagesFromDirectory loads all .pt files in a directory as pages.
This is mainly used to load Zope2 skin templates so they can be used
in five skins and layers. -->
<five:pagesFromDirectory
module="Products.Five.tests.products.FiveTest"
directory="pages"
/>
<browser:defaultView
for=".interfaces.ICallableSimpleContent"
name="__call__"
/>
<!-- this tests whether five:defaultViewable can be called on a class that
already provides index_html, such as our
IndexSimpleContent
-->
<five:defaultViewable class=".simplecontent.IndexSimpleContent" />
<browser:defaultView
for=".interfaces.IIndexSimpleContent"
name="index_html"
/>
<adapter
for=".interfaces.IAdaptable"
provides=".interfaces.IAdapted"
factory=".classes.Adapter"
/>
<!-- attribute page -->
<browser:page
for=".interfaces.ISimpleContent"
class=".browser.SimpleContentView"
attribute="eagle"
name="eagle.txt"
permission="zope2.ViewManagementScreens"
/>
<browser:page
for=".interfaces.ISimpleContent"
class=".browser.SimpleContentView"
name="eagle.method"
permission="zope2.ViewManagementScreens"
allowed_attributes="eagle"
/>
<browser:page
for=".interfaces.IFancyContent"
class=".browser.FancyContentView"
attribute="view"
name="fancy"
permission="zope2.Public"
/>
<browser:pages
for=".interfaces.ISimpleContent"
class=".browser.NoDocstringView"
permission="zope2.Public">
<browser:page
name="nodoc-method"
attribute="method"
/>
<browser:page
name="nodoc-function"
attribute="function"
/>
<browser:page
name="nodoc-object"
attribute="object"
/>
</browser:pages>
<!-- attribute page -->
<browser:pages
for=".interfaces.ISimpleContent"
class=".browser.SimpleContentView"
permission="zope2.ViewManagementScreens"
>
<browser:page
name="eagle-page.txt"
attribute="eagle"
/>
<browser:page
name="mouse-page.txt"
attribute="mouse"
/>
</browser:pages>
<!-- template/class page -->
<browser:page
for=".interfaces.ISimpleContent"
class=".browser.SimpleContentView"
template="falcon.pt"
name="falcon.html"
permission="zope2.ViewManagementScreens"
/>
<!-- template page (with simple python expression) -->
<browser:page
for=".interfaces.ISimpleContent"
template="owl.pt"
name="owl.html"
permission="zope2.ViewManagementScreens"
/>
<!-- template page which calls on context using python -->
<browser:page
for=".interfaces.ISimpleContent"
template="flamingo.pt"
name="flamingo.html"
permission="zope2.ViewManagementScreens"
/>
<!-- template page which calls on context using path -->
<browser:page
for=".interfaces.ISimpleContent"
template="flamingo2.pt"
name="flamingo2.html"
permission="zope2.ViewManagementScreens"
/>
<!-- template/class page which calls on context, view, views -->
<browser:page
for=".interfaces.ISimpleContent"
class=".browser.SimpleContentView"
template="condor.pt"
name="condor.html"
permission="zope2.ViewManagementScreens"
/>
<!-- test TALES -->
<browser:page
for=".interfaces.ISimpleContent"
template="ostrich.pt"
name="ostrich.html"
permission="zope2.ViewManagementScreens"
/>
<browser:page
for=".interfaces.ISimpleContent"
template="ostrich2.pt"
name="ostrich2.html"
permission="zope2.ViewManagementScreens"
/>
<browser:page
for=".interfaces.ISimpleContent"
template="tales_traversal.pt"
name="tales_traversal.html"
permission="zope2.ViewManagementScreens"
/>
<!-- template security -->
<browser:page
for=".interfaces.ISimpleContent"
template="security.pt"
name="security.html"
permission="zope2.View"
/>
<!-- macro page -->
<browser:page
for=".interfaces.ISimpleContent"
template="bird.pt"
name="bird.pt"
permission="zope2.ViewManagementScreens"
/>
<!-- macro aggregation page -->
<browser:page
for="*"
name="fivetest_macros"
permission="zope2.View"
class=".browser.StandardMacros"
allowed_interface="zope.interface.common.mapping.IItemMapping"
/>
<browser:page
for=".interfaces.ISimpleContent"
template="bird.pt"
name="bird_macros"
permission="zope2.ViewManagementScreens"
/>
<browser:page
for=".interfaces.ISimpleContent"
template="dog.pt"
name="dog_macros"
permission="zope2.ViewManagementScreens"
/>
<!-- template page that uses macro page -->
<browser:page
for=".interfaces.ISimpleContent"
class=".browser.SimpleContentView"
template="seagull.pt"
name="seagull.html"
permission="zope2.ViewManagementScreens"
/>
<browser:page
for=".interfaces.ISimpleContent"
class=".browser.SimpleContentView"
template="parakeet.pt"
name="parakeet.html"
permission="zope2.ViewManagementScreens"
/>
<!-- a publicly accessible page, attribute, template, template/class -->
<browser:page
for=".interfaces.ISimpleContent"
class=".browser.SimpleContentView"
attribute="eagle"
name="public_attribute_page"
permission="zope2.Public"
/>
<browser:page
for=".interfaces.ISimpleContent"
template="owl.pt"
name="public_template_page"
permission="zope2.Public"
/>
<browser:page
for=".interfaces.ISimpleContent"
class=".browser.SimpleContentView"
template="falcon.pt"
name="public_template_class_page"
permission="zope2.Public"
/>
<!-- a couple simple resources -->
<browser:resource
template="cockatiel.pt"
name="cockatiel.html"
permission="zope2.ViewManagementScreens"
/>
<browser:resource
file="style.css"
name="style.css"
permission="zope2.ViewManagementScreens"
/>
<browser:resource
image="pattern.png"
name="pattern.png"
permission="zope2.ViewManagementScreens"
/>
<browser:resourceDirectory
name="fivetest_resources"
directory="."
permission="zope2.ViewManagementScreens"
/>
<!-- browser forms -->
<five:traversable class=".simplecontent.FieldSimpleContent" />
<browser:editform
schema=".interfaces.IFieldSimpleContent"
for=".interfaces.IFieldSimpleContent"
name="edit.html"
permission="zope2.Public"
/>
<five:traversable class=".helpers.FiveTraversableFolder" />
<browser:addform
schema=".interfaces.IFieldSimpleContent"
content_factory=".simplecontent.FieldSimpleContent"
name="addsimplecontent.html"
permission="zope2.Public"
/>
<!-- stuff that we'll override in overrides.zcml -->
<browser:page
for=".interfaces.ISimpleContent"
class=".browser.SimpleContentView"
attribute="eagle"
name="overridden_view"
permission="zope2.Public"
/>
<adapter
for=".interfaces.IOrigin"
provides=".interfaces.IDestination"
factory=".classes.OriginalAdapter"
/>
<!-- browser:page directives with new style classes are ignored -->
<browser:page
for=".interfaces.ISimpleContent"
class=".browser.NewStyleClass"
name="invalid_page"
attribute="method"
permission="zope2.Public"
/>
<!-- browser menu support -->
<browser:menu
id="testmenu"
title="Test menu" />
<browser:menuItem
for=".interfaces.ISimpleContent"
menu="testmenu"
title="Test Menu Item"
action="seagull.html"
description="This is a test menu item"
permission="zope2.Public"
/>
<browser:menuItems
for=".interfaces.ISimpleContent"
menu="testmenu">
<menuItem
title="Test Menu Item 2"
action="parakeet.html"
description="This is a test menu item"
permission="zope2.Public"
/>
<menuItem
title="Test Menu Item 3"
action="falcon.html"
description="This is a test menu item"
permission="zope2.Public"
/>
</browser:menuItems>
<!-- page in a menu -->
<browser:page
for=".interfaces.ISimpleContent"
class=".browser.SimpleContentView"
template="parakeet.pt"
name="parakeet_beyond.html"
permission="zope2.ViewManagementScreens"
title="A page based entry"
menu="testmenu"
/>
<browser:editform
schema=".interfaces.IFieldSimpleContent"
for=".interfaces.IFieldSimpleContent"
name="edit2.html"
permission="zope2.Public"
title="An edit form based entry"
menu="testmenu"
/>
<!-- subscribe to all events -->
<five:sendEvents
class=".simplecontent.SimpleContent"
/>
<subscriber
factory=".subscriber.objectEventSubscriber"
for="zope.app.event.interfaces.IObjectEvent"
/>
<subscriber
factory=".subscriber.objectMovedEventSubscriber"
for="zope.app.container.interfaces.IObjectMovedEvent"
/>
<subscriber
factory=".subscriber.objectAddedEventSubscriber"
for="zope.app.container.interfaces.IObjectAddedEvent"
/>
<subscriber
factory=".subscriber.objectCopiedEventSubscriber"
for="zope.app.event.interfaces.IObjectCopiedEvent"
/>
<subscriber
factory=".subscriber.objectRemovedEventSubscriber"
for="zope.app.container.interfaces.IObjectRemovedEvent"
/>
<!-- as new style classes are ignored, zope.app.form.browser
can be imported -->
<include package="zope.app.form.browser"/>
</configure>
<html metal:define-macro="dogmacro"><head><title>dog macro</title></head><body>Breed: <metal:block define-slot="breed" /></body></html>
<p>The falcon has taken flight</p>
\ No newline at end of file
import Acquisition
from AccessControl import ClassSecurityInfo
from OFS.SimpleItem import SimpleItem
from Globals import InitializeClass
from zope.interface import implements
from interfaces import IFancyContent
class FancyAttribute(Acquisition.Explicit):
"""Doc test fanatics"""
def __init__(self, name):
self.name = name
security = ClassSecurityInfo()
security.declarePublic('index_html')
def index_html(self, REQUEST):
"""Doc test fanatics"""
return self.name
InitializeClass(FancyAttribute)
class FancyContent(SimpleItem):
"""A class that already comes with its own __bobo_traverse__ handler.
Quite fancy indeed."""
implements(IFancyContent)
meta_type = "Fancy Content"
security = ClassSecurityInfo()
def __bobo_traverse__(self, REQUEST, name):
return FancyAttribute(name).__of__(self)
InitializeClass(FancyContent)
def manage_addFancyContent(self, id, REQUEST=None):
"""Add the fancy fancy content."""
id = self._setObject(id, FancyContent(id))
return ''
<p tal:content="python:context.mymethod()">Replaced</p>
\ No newline at end of file
<p tal:content="context/mymethod">Replaced</p>
\ No newline at end of file
import urllib
def add_and_edit(self, id, REQUEST):
"""Helper function to point to the object's management screen if
'Add and Edit' button is pressed.
id -- id of the object we just added
"""
if REQUEST is None:
return
try:
u = self.DestinationURL()
except:
u = REQUEST['URL1']
if REQUEST.has_key('submit_edit'):
u = "%s/%s" % (u, urllib.quote(id))
REQUEST.RESPONSE.redirect(u+'/manage_main')
from OFS.Folder import Folder
class NoVerifyPasteFolder(Folder):
"""Folder that does not perform paste verification.
Used by test_events
"""
def _verifyObjectPaste(self, object, validate_src=1):
pass
def manage_addNoVerifyPasteFolder(container, id, title=''):
container._setObject(id, NoVerifyPasteFolder())
folder = container[id]
folder.id = id
folder.title = title
class FiveTraversableFolder(Folder):
"""Folder that is declared Five traversable, see configure.zcml
"""
pass
def manage_addFiveTraversableFolder(container, id, title=''):
container._setObject(id, FiveTraversableFolder())
folder = container[id]
folder.id = id
folder.title = title
from zope.interface import Interface
from zope.schema import Text, TextLine
class IAdaptable(Interface):
"""This is a Zope 3 interface.
"""
def method():
"""This method will be adapted
"""
class IAdapted(Interface):
"""The interface we adapt to.
"""
def adaptedMethod():
"""A method to adapt.
"""
class IOrigin(Interface):
"""Something we'll adapt"""
class IDestination(Interface):
"""The result of an adaption"""
def method():
"""Do something"""
class ISimpleContent(Interface):
pass
class ICallableSimpleContent(ISimpleContent):
pass
class IIndexSimpleContent(ISimpleContent):
pass
class IFancyContent(Interface):
pass
class IFieldSimpleContent(ISimpleContent):
title = TextLine(
title=u"Title",
description=u"A short description of the event.",
default=u"",
required=True)
description = Text(
title=u"Description",
description=u"A long description of the event.",
default=u"",
required=False)
<ul>
<li tal:repeat="item python:['Alpha', 'Beta', 'Gamma']" tal:content="item"/>
</ul>
\ No newline at end of file
<ul>
<li tal:repeat="item python:['Alpha', 'Beta', 'Gamma']" tal:content="python:repeat['item'].index"/>
</ul>
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser">
<!-- mouse instead of eagle -->
<browser:page
for=".interfaces.ISimpleContent"
class=".browser.SimpleContentView"
attribute="mouse"
name="overridden_view"
permission="zope2.Public"
/>
<!-- OverrideAdapter instead of OriginalAdapter -->
<adapter
for=".interfaces.IOrigin"
provides=".interfaces.IDestination"
factory=".classes.OverrideAdapter"
/>
</configure>
<p tal:content="python:1+1">Some content</p>
\ No newline at end of file
<html metal:use-macro="context/@@fivetest_macros/birdmacro"><head><title>bird macro</title></head><body><metal:block fill-slot="color">green</metal:block><metal:block fill-slot="birdinfo"><img alt="" src="" tal:attributes="src context/++resource++pattern.png" /></metal:block></body></html>
<html metal:use-macro="views/bird.pt/birdmacro"><metal:block fill-slot="color">gray</metal:block></html>
<div tal:define="comment string:Testing unrestricted code"
tal:content="python:None.__class__.__name__" />
<div tal:define="comment string:Testing unrestricted modules access;
smtpd nocall:modules/smtpd"
tal:content="python:smtpd.__name__" />
from OFS.SimpleItem import SimpleItem
from Globals import InitializeClass
from AccessControl import ClassSecurityInfo
from helpers import add_and_edit
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
from zope.interface import implements
from interfaces import ISimpleContent, ICallableSimpleContent,\
IIndexSimpleContent, IFieldSimpleContent
class SimpleContent(SimpleItem):
implements(ISimpleContent)
meta_type = 'Five SimpleContent'
security = ClassSecurityInfo()
def __init__(self, id, title):
self.id = id
self.title = title
security.declarePublic('mymethod')
def mymethod(self):
return "Hello world"
security.declarePublic('direct')
def direct(self):
"""Should be able to traverse directly to this as there is no view.
"""
return "Direct traversal worked"
InitializeClass(SimpleContent)
class CallableSimpleContent(SimpleItem):
"""A Viewable piece of content"""
implements(ICallableSimpleContent)
meta_type = "Five CallableSimpleContent"
def __call__(self, *args, **kw):
""" """
return "Default __call__ called"
InitializeClass(CallableSimpleContent)
class IndexSimpleContent(SimpleItem):
"""A Viewable piece of content"""
implements(IIndexSimpleContent)
meta_type = 'Five IndexSimpleContent'
def index_html(self, *args, **kw):
""" """
return "Default index_html called"
InitializeClass(IndexSimpleContent)
class FieldSimpleContent(SimpleContent):
"""A Viewable piece of content with fields"""
implements(IFieldSimpleContent)
meta_type = 'Five FieldSimpleContent'
InitializeClass(FieldSimpleContent)
manage_addSimpleContentForm = PageTemplateFile(
"www/simpleContentAdd", globals(),
__name__ = 'manage_addSimpleContentForm')
def manage_addSimpleContent(self, id, title, REQUEST=None):
"""Add the simple content."""
id = self._setObject(id, SimpleContent(id, title))
add_and_edit(self, id, REQUEST)
return ''
def manage_addCallableSimpleContent(self, id, title, REQUEST=None):
"""Add the viewable simple content."""
id = self._setObject(id, CallableSimpleContent(id, title))
add_and_edit(self, id, REQUEST)
return ''
def manage_addIndexSimpleContent(self, id, title, REQUEST=None):
"""Add the viewable simple content."""
id = self._setObject(id, IndexSimpleContent(id, title))
add_and_edit(self, id, REQUEST)
return ''
manage_addFieldSimpleContentForm = PageTemplateFile(
"www/fieldSimpleContentAdd", globals(),
__name__ = 'manage_addFieldSimpleContentForm')
def manage_addFieldSimpleContent(self, id, title, REQUEST=None):
"""Add the viewable simple content."""
id = self._setObject(id, FieldSimpleContent(id, title))
add_and_edit(self, id, REQUEST)
return ''
class EventCatcher:
def __init__(self):
self._events = []
def subscriber(self, event):
self._events.append(event)
def getEvents(self):
return self._events
def clear(self):
self._events = []
objectEventCatcher = EventCatcher()
objectEventSubscriber = objectEventCatcher.subscriber
objectMovedEventCatcher = EventCatcher()
objectMovedEventSubscriber = objectMovedEventCatcher.subscriber
objectAddedEventCatcher = EventCatcher()
objectAddedEventSubscriber = objectAddedEventCatcher.subscriber
objectCopiedEventCatcher = EventCatcher()
objectCopiedEventSubscriber = objectCopiedEventCatcher.subscriber
objectRemovedEventCatcher = EventCatcher()
objectRemovedEventSubscriber = objectRemovedEventCatcher.subscriber
def clear():
objectEventCatcher.clear()
objectMovedEventCatcher.clear()
objectAddedEventCatcher.clear()
objectCopiedEventCatcher.clear()
objectRemovedEventCatcher.clear()
<p tal:content="context/non_existent_thingie/fubared|context/getId">dunno</p>
<p tal:content="context/test_folder_1_/test_folder_1_/getId">dunno</p>
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:five="http://namespaces.zope.org/five">
<redefinePermission from="zope2.Public" to="zope.Public" />
<include file="configure.zcml" />
<includeOverrides file="overrides.zcml" />
</configure>
<h1 tal:replace="structure here/manage_page_header">Header</h1>
<h2 tal:define="form_title string:Add Field Simple Content"
tal:replace="structure here/manage_form_title">Form Title</h2>
<p class="form-help">
Add Field Simple Content
</p>
<form action="manage_addFieldSimpleContent" method="post">
<table cellspacing="0" cellpadding="2" border="0">
<tr>
<td align="left" valign="top">
<div class="form-label">
Id
</div>
</td>
<td align="left" valign="top">
<input type="text" name="id" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Title
</div>
</td>
<td align="left" valign="top">
<input type="text" name="title" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
</td>
<td align="left" valign="top">
<div class="form-element">
<input class="form-element" type="submit" name="submit_add"
value=" Add " />
</div>
</td>
</tr>
</table>
</form>
<h1 tal:replace="structure here/manage_page_footer">Footer</h1>
<h1 tal:replace="structure here/manage_page_header">Header</h1>
<h2 tal:define="form_title string:Add Simple Content"
tal:replace="structure here/manage_form_title">Form Title</h2>
<p class="form-help">
Add Simple Content
</p>
<form action="manage_addSimpleContent" method="post">
<table cellspacing="0" cellpadding="2" border="0">
<tr>
<td align="left" valign="top">
<div class="form-label">
Id
</div>
</td>
<td align="left" valign="top">
<input type="text" name="id" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
<div class="form-label">
Title
</div>
</td>
<td align="left" valign="top">
<input type="text" name="title" size="40" />
</td>
</tr>
<tr>
<td align="left" valign="top">
</td>
<td align="left" valign="top">
<div class="form-element">
<input class="form-element" type="submit" name="submit_add"
value=" Add " />
</div>
</td>
</tr>
</table>
</form>
<h1 tal:replace="structure here/manage_page_footer">Footer</h1>
#
# Runs all tests in the current directory
#
# Execute like:
# python runalltests.py
#
# Alternatively use the testrunner:
# python /path/to/Zope/utilities/testrunner.py -qa
#
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
import unittest
TestRunner = unittest.TextTestRunner
suite = unittest.TestSuite()
tests = os.listdir(os.curdir)
tests = [n[:-3] for n in tests if n.startswith('test') and n.endswith('.py')]
for test in tests:
m = __import__(test)
if hasattr(m, 'test_suite'):
suite.addTest(m.test_suite())
if __name__ == '__main__':
TestRunner().run(suite)
#TestRunner(descriptions=1, verbosity=2).run(suite)
##############################################################################
#
# Copyright (c) 2004 Five Contributors. All rights reserved.
#
# This software is distributed under the terms of the Zope Public
# License (ZPL) v2.1. See COPYING.txt for more information.
#
##############################################################################
""" Unit tests for Z2 -> Z3 bridge utilities.
$Id:$
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
import unittest
#------------------------------------------------------------------------------
# Running these tests
# ===================
#
# I (Tres) can't figure out your testing framework. These tests run
# in a "normal" Z27 + Z3 + Five instance home via the following:
#
# $ bin/zopectl run Products/Five/tests/test_bridge.py
#------------------------------------------------------------------------------
from Interface import Interface as Z2_Interface
from Interface import Attribute as Z2_Attribute
from zope.interface import Interface as Z3_Interface
from zope.interface import Attribute as Z3_Attribute
from zope.interface import Attribute as Z3_Method
class BridgeTests(unittest.TestCase):
def test_fromZ2Interface_invalid(self):
from Products.Five.bridge import fromZ2Interface
self.assertRaises(ValueError, fromZ2Interface, None)
self.assertRaises(ValueError, fromZ2Interface, object())
class IZ3_NotAllowed(Z3_Interface):
pass
self.assertRaises(ValueError, fromZ2Interface, IZ3_NotAllowed)
def test_fromZ2Interface_empty(self):
from Products.Five.bridge import fromZ2Interface
class IEmpty(Z2_Interface):
pass
converted = fromZ2Interface(IEmpty)
self.failUnless(Z3_Interface.isEqualOrExtendedBy(converted))
self.assertEqual(len(converted.names()), 0)
def test_fromZ2Interface_attributes(self):
from Products.Five.bridge import fromZ2Interface
class IAttributes(Z2_Interface):
one = Z2_Attribute('one', 'One attribute')
another = Z2_Attribute('another', 'Another attribute')
converted = fromZ2Interface(IAttributes)
self.failUnless(Z3_Interface.isEqualOrExtendedBy(converted))
self.assertEqual(len(converted.names()), 2)
self.failUnless('one' in converted.names())
self.failUnless('another' in converted.names())
one = converted.getDescriptionFor('one')
self.failUnless(isinstance(one, Z3_Attribute))
self.assertEqual(one.getName(), 'one')
self.assertEqual(one.getDoc(), 'One attribute')
another = converted.getDescriptionFor('another')
self.failUnless(isinstance(another, Z3_Attribute))
self.assertEqual(another.getName(), 'another')
self.assertEqual(another.getDoc(), 'Another attribute')
def test_fromZ2Interface_methods(self):
from Products.Five.bridge import fromZ2Interface
class IMethods(Z2_Interface):
def one():
"""One method."""
def another(arg1, arg2):
"""Another method, taking arguments."""
converted = fromZ2Interface(IMethods)
self.failUnless(Z3_Interface.isEqualOrExtendedBy(converted))
self.assertEqual(len(converted.names()), 2)
self.failUnless('one' in converted.names())
self.failUnless('another' in converted.names())
one = converted.getDescriptionFor('one')
self.failUnless(isinstance(one, Z3_Method))
self.assertEqual(one.getName(), 'one')
self.assertEqual(one.getDoc(), 'One method.')
another = converted.getDescriptionFor('another')
self.failUnless(isinstance(another, Z3_Method))
self.assertEqual(another.getName(), 'another')
self.assertEqual(another.getDoc(), 'Another method, taking arguments.')
def test_suite():
return unittest.defaultTestLoader.loadTestsFromTestCase( BridgeTests )
if __name__ == '__main__':
framework()
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
from Products.Five.tests.fivetest import *
from AccessControl import Unauthorized
from zope.app.form.browser.submit import Update
from Products.Five.tests.products.FiveTest.simplecontent import manage_addFieldSimpleContent
from Products.Five.tests.products.FiveTest.helpers import manage_addFiveTraversableFolder
class EditFormTest(Functional, FiveTestCase):
def afterSetUp(self):
manage_addFieldSimpleContent(self.folder, 'edittest', 'Test')
uf = self.folder.acl_users
uf._doAddUser('viewer', 'secret', [], [])
uf._doAddUser('manager', 'r00t', ['Manager'], [])
def test_editform(self):
response = self.publish('/test_folder_1_/edittest/edit.html',
basic='manager:r00t')
# we're using a GET request to post variables, but seems to be
# the easiest..
response = self.publish(
'/test_folder_1_/edittest/edit.html?%s=1&field.title=FooTitle&field.description=FooDescription' % Update,
basic='manager:r00t')
self.assertEquals('FooTitle', self.folder.edittest.title)
self.assertEquals('FooDescription', self.folder.edittest.description)
def test_editform_invalid(self):
# missing title, which is required
self.folder.edittest.description = ''
response = self.publish(
'/test_folder_1_/edittest/edit.html?%s=1&field.title=&field.description=BarDescription' % Update,
basic='manager:r00t')
# we expect that we get a 200 Ok
self.assertEqual(200, response.getStatus())
self.assertEquals('Test', self.folder.edittest.title)
self.assertEquals('', self.folder.edittest.description)
def test_addform(self):
manage_addFiveTraversableFolder(self.folder, 'ftf')
self.folder = self.folder.ftf
response = self.publish('/test_folder_1_/ftf/+/addsimplecontent.html',
basic='manager:r00t')
# we're using a GET request to post variables, but seems to be
# the easiest..
response = self.publish(
'/test_folder_1_/ftf/+/addsimplecontent.html?%s=1&add_input_name=alpha&field.title=FooTitle&field.description=FooDescription' % Update,
basic='manager:r00t')
# we expect to get a 302 (redirect)
self.assertEquals(302, response.getStatus())
# we expect the object to be there with the right id
self.assertEquals('alpha', self.folder.alpha.id)
self.assertEquals('FooTitle', self.folder.alpha.title)
self.assertEquals('FooDescription', self.folder.alpha.description)
def test_suite():
from unittest import TestSuite, makeSuite
suite = TestSuite()
suite.addTest(makeSuite(EditFormTest))
return suite
if __name__ == '__main__':
framework()
# test events triggered by Five
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
from Products.Five.tests.fivetest import *
from Products.Five.tests.products.FiveTest.subscriber import clear
from Products.Five.tests.products.FiveTest.subscriber import objectEventCatcher, \
objectAddedEventCatcher, objectMovedEventCatcher, \
objectCopiedEventCatcher, objectRemovedEventCatcher
from Products.Five.tests.products.FiveTest.simplecontent import manage_addSimpleContent
from Products.Five.tests.products.FiveTest.helpers import manage_addNoVerifyPasteFolder
class EventTest(FiveTestCase):
def afterSetUp(self):
manage_addNoVerifyPasteFolder(self.folder, 'npvf')
self.folder = self.folder.npvf
uf = self.folder.acl_users
uf._doAddUser('manager', 'r00t', ['Manager'], [])
self.login('manager')
self.setPermissions(
standard_permissions + ['Copy or Move'], 'Manager')
# clear all events
clear()
def test_added_event(self):
manage_addSimpleContent(self.folder, 'foo', 'Foo')
foo = self.folder.foo
events = objectEventCatcher.getEvents()
self.assertEquals(1, len(events))
self.assertEquals(foo.getPhysicalPath(),
events[0].object.getPhysicalPath())
events = objectAddedEventCatcher.getEvents()
self.assertEquals(1, len(events))
self.assertEquals(foo.getPhysicalPath(),
events[0].object.getPhysicalPath())
self.assertEquals(foo.aq_parent.getPhysicalPath(),
events[0].newParent.getPhysicalPath())
def test_moved_event(self):
manage_addSimpleContent(self.folder, 'foo', 'Foo')
# somehow we need to at least commit a subtransaction to make
# renaming succeed
get_transaction().commit(1)
self.folder.manage_renameObject('foo', 'bar')
bar = self.folder.bar
events = objectEventCatcher.getEvents()
self.assertEquals(3, len(events))
# will have new location so should still match
self.assertEquals(bar.getPhysicalPath(),
events[0].object.getPhysicalPath())
self.assertEquals(bar.getPhysicalPath(),
events[1].object.getPhysicalPath())
# removed event
self.assertEquals('foo',
events[1].oldName)
self.assertEquals(None,
events[1].newName)
# moved event
self.assertEquals('foo',
events[2].oldName)
self.assertEquals('bar',
events[2].newName)
self.assertEquals(self.folder.getPhysicalPath(),
events[2].oldParent.getPhysicalPath())
self.assertEquals(self.folder.getPhysicalPath(),
events[2].oldParent.getPhysicalPath())
def test_moved_event2(self):
# move from one folder to another
manage_addNoVerifyPasteFolder(self.folder, 'folder1', 'Folder1')
folder1 = self.folder.folder1
manage_addNoVerifyPasteFolder(self.folder, 'folder2', 'Folder2')
folder2 = self.folder.folder2
manage_addSimpleContent(folder1, 'foo', 'Foo')
foo = folder1.foo
# need to trigger subtransaction before copy/paste can work
get_transaction().commit(1)
cb = folder1.manage_cutObjects(['foo'])
folder2.manage_pasteObjects(cb)
newfoo = folder2.foo
events = objectMovedEventCatcher.getEvents()
self.assertEquals(3, len(events))
self.assertEquals(1, len(objectAddedEventCatcher.getEvents()))
# removed event
self.assertEquals(folder1.getPhysicalPath(),
events[1].oldParent.getPhysicalPath())
self.assertEquals(None,
events[1].newParent)
# moved event
self.assertEquals(newfoo.getPhysicalPath(),
events[2].object.getPhysicalPath())
self.assertEquals(folder1.getPhysicalPath(),
events[2].oldParent.getPhysicalPath())
self.assertEquals(folder2.getPhysicalPath(),
events[2].newParent.getPhysicalPath())
self.assertEquals('foo',
events[2].oldName)
self.assertEquals('foo',
events[2].newName)
def test_copied_event(self):
manage_addSimpleContent(self.folder, 'foo', 'Foo')
manage_addNoVerifyPasteFolder(self.folder, 'folder1')
folder1 = self.folder.folder1
# need to trigger subtransaction before copy/paste can work
get_transaction().commit(1)
cb = self.folder.manage_copyObjects(['foo'])
folder1.manage_pasteObjects(cb)
foo_copy = folder1.foo
events = objectCopiedEventCatcher.getEvents()
self.assertEquals(1, len(events))
self.assertEquals(foo_copy.getPhysicalPath(),
events[0].object.getPhysicalPath())
events = objectAddedEventCatcher.getEvents()
self.assertEquals(2, len(events))
self.assertEquals(foo_copy.getPhysicalPath(),
events[1].object.getPhysicalPath())
def test_removed_event(self):
manage_addSimpleContent(self.folder, 'foo', 'Foo')
self.folder.manage_delObjects(['foo'])
events = objectRemovedEventCatcher.getEvents()
self.assertEquals(1, len(events))
self.assertEquals('foo', events[0].object.id)
def test_suite():
from unittest import TestSuite, makeSuite
suite = TestSuite()
suite.addTest(makeSuite(EventTest))
return suite
if __name__ == '__main__':
framework()
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
from Products.Five.tests.fivetest import *
import re
import glob
import unittest
import zope
from zope.interface import directlyProvides, Interface, implements
from zope.component import getViewProviding
from zope.schema import Choice, TextLine
from zope.app.form.interfaces import IInputWidget
from zope.app.traversing.browser.interfaces import IAbsoluteURL
from zope.app.traversing.interfaces import IContainmentRoot
from Products.Five.tests.products.FiveTest.classes import Adaptable, Origin
from Products.Five.tests.products.FiveTest.interfaces import IAdapted, IDestination
from Products.Five.tests.products.FiveTest.browser import SimpleContentView
from Products.Five.resource import Resource, PageTemplateResource
from Products.Five.traversable import FakeRequest
from Products.Five.fiveconfigure import classDefaultViewable
from OFS.Traversable import Traversable
from Products.Five.tests.products.FiveTest.simplecontent import manage_addSimpleContent
from Products.Five.tests.products.FiveTest.simplecontent import manage_addCallableSimpleContent
from Products.Five.tests.products.FiveTest.simplecontent import manage_addIndexSimpleContent
from Products.Five.tests.products.FiveTest.fancycontent import manage_addFancyContent
from Products.Five.tests.products import FiveTest
_prefix = os.path.dirname(FiveTest.__file__)
dir_resource_names = [os.path.basename(r)
for r in (glob.glob('%s/*.png' % _prefix) +
glob.glob('%s/*.pt' % _prefix) +
glob.glob('%s/[a-z]*.py' % _prefix) +
glob.glob('%s/*.css' % _prefix))]
def normalize_html(s):
s = re.sub(r"[ \t\n]+", "", s)
return s
class FiveTest(FiveTestCase):
def afterSetUp(self):
manage_addSimpleContent(self.folder, 'testoid', 'Testoid')
manage_addCallableSimpleContent(self.folder, 'testcall', 'TestCall')
manage_addIndexSimpleContent(self.folder, 'testindex', 'TestIndex')
uf = self.folder.acl_users
uf._doAddUser('manager', 'r00t', ['Manager'], [])
self.login('manager')
def test_adapters(self):
obj = Adaptable()
adapted = IAdapted(obj)
self.assertEquals(
"Adapted: The method",
adapted.adaptedMethod())
def test_adapters2(self):
obj = Adaptable()
adapted = IAdapted(obj)
self.assertEquals(
"Adapted: The method",
adapted.adaptedMethod())
def test_overrides(self):
origin = Origin()
dest = IDestination(origin)
self.assertEquals(dest.method(), "Overridden")
view = self.folder.unrestrictedTraverse('testoid/overridden_view')
self.assertEquals(view(), "The mouse has been eaten by the eagle")
def test_attribute_view(self):
view = self.folder.unrestrictedTraverse('testoid/eagle.txt')
self.assert_(isinstance(view, SimpleContentView))
self.assertEquals('The eagle has landed', view())
def test_existing_docstrings_arent_modified(self):
view = self.folder.unrestrictedTraverse('testoid/eagle.txt')
self.assertEquals(view.eagle.__doc__, SimpleContentView.eagle.__doc__)
def test_pages_view(self):
view = self.folder.unrestrictedTraverse('testoid/eagle-page.txt')
self.assert_(isinstance(view, SimpleContentView))
self.assertEquals('The eagle has landed', view())
view = self.folder.unrestrictedTraverse('testoid/mouse-page.txt')
self.assert_(isinstance(view, SimpleContentView))
self.assertEquals('The mouse has been eaten by the eagle', view())
def test_template_view(self):
view = self.folder.unrestrictedTraverse('testoid/falcon.html')
self.assert_(isinstance(view, SimpleContentView))
self.assertEquals(u'<p>The falcon has taken flight</p>\n', view())
def test_template_view_without_class(self):
view = self.folder.unrestrictedTraverse('testoid/owl.html')
self.assertEquals(u'<p>2</p>\n', view())
def test_template_view_context(self):
view = self.folder.unrestrictedTraverse('testoid/flamingo.html')
self.assertEquals(u'<p>Hello world</p>\n', view())
def test_template_view_context_path(self):
view = self.folder.unrestrictedTraverse('testoid/flamingo2.html')
self.assertEquals(u'<p>Hello world</p>\n', view())
def test_template_view_resource_traversal(self):
view = self.folder.unrestrictedTraverse('testoid/parakeet.html')
expected = """\
<html>
<head>
<title>bird macro</title>
</head>
<body>
Color: green
<img alt="" src="http://nohost/test_folder_1_/testoid/++resource++pattern.png" />
</body>
</html>
"""
expected = normalize_html(expected)
self.assertEquals(expected, normalize_html(view()))
def test_view_backwards_compatibility(self):
old_view = self.folder.unrestrictedTraverse('testoid/direct')
self.assertEquals('Direct traversal worked', old_view())
def test_zpt_things(self):
view = self.folder.unrestrictedTraverse('testoid/condor.html')
expected = """\
<p>Hello world</p>
<p>The eagle has landed</p>
<p>Hello world</p>
"""
self.assertEquals(expected, view())
def test_repeat(self):
view = self.folder.unrestrictedTraverse('testoid/ostrich.html')
expected = """\
<ul>
<li>Alpha</li>
<li>Beta</li>
<li>Gamma</li>
</ul>
"""
self.assertEquals(expected, view())
def test_macro_access(self):
view = self.folder.unrestrictedTraverse('testoid/seagull.html')
self.assertEquals('<html><head><title>bird macro</title></head><body>Color: gray</body></html>\n', view())
def test_repeat_iterator(self):
view = self.folder.unrestrictedTraverse('testoid/ostrich2.html')
expected = """\
<ul>
<li>0</li>
<li>1</li>
<li>2</li>
</ul>
"""
self.assertEquals(expected, view())
def test_tales_traversal(self):
view = self.folder.unrestrictedTraverse('testoid/tales_traversal.html')
expected = """\
<p>testoid</p>
<p>test_folder_1_</p>
"""
self.assertEquals(expected, view())
def test_zpt_security(self):
self.logout()
view = self.folder.unrestrictedTraverse('testoid/security.html')
expected = """\
<div>NoneType</div>
<div>smtpd</div>
"""
self.assertEquals(expected, view())
def test_template_resource(self):
resource = self.folder.unrestrictedTraverse('testoid/++resource++cockatiel.html')
self.assert_(isinstance(resource, Resource))
expected = """\
<p>Have you ever seen a cockatiel?</p>
<p>maybe</p>
"""
self.assertEquals(expected, resource())
def test_file_resource(self):
resource = self.folder.unrestrictedTraverse('testoid/++resource++style.css')
self.assert_(isinstance(resource, Resource))
expected = 'http://nohost/test_folder_1_/testoid/++resource++style.css'
self.assertEquals(expected, resource())
def test_image_resource(self):
resource = self.folder.unrestrictedTraverse('testoid/++resource++pattern.png')
expected = 'http://nohost/test_folder_1_/testoid/++resource++pattern.png'
self.assert_(isinstance(resource, Resource))
self.assertEquals(expected, resource())
def test_resource_directory(self):
base = 'testoid/++resource++fivetest_resources/%s'
base_url = 'test_folder_1_/%s' % base
for r in dir_resource_names:
resource = self.folder.unrestrictedTraverse(base % r)
self.assert_(isinstance(resource, Resource))
# PageTemplateResource's __call__ renders the template
if not isinstance(resource, PageTemplateResource):
self.assertEquals(resource(), base_url % r)
abs_url = self.folder.unrestrictedTraverse(base % '')()
expected = 'http://nohost/test_folder_1_/testoid/++resource++fivetest_resources'
self.assertEquals(abs_url, expected)
def test_breadcrumbs(self):
view = self.folder.unrestrictedTraverse('testoid/@@absolute_url')
expected = (
{'url': 'http://nohost', 'name': ''},
{'url': 'http://nohost/test_folder_1_', 'name': 'test_folder_1_'},
{'url': 'http://nohost/test_folder_1_/testoid', 'name': 'testoid'})
self.assertEquals(expected, view.breadcrumbs())
def test_virtualhost_breadcrumbs(self):
# Get REQUEST in shape
request = self.request = self.app.REQUEST
request['PARENTS'] = [self.folder.test_folder_1_]
request.setServerURL(
protocol='http', hostname='foo.bar.com', port='80')
request.setVirtualRoot('')
view = self.folder.unrestrictedTraverse('testoid/@@absolute_url')
expected = (
{'url': 'http://foo.bar.com', 'name': 'test_folder_1_'},
{'url': 'http://foo.bar.com/testoid', 'name': 'testoid'})
self.assertEquals(expected, view.breadcrumbs())
def test_containement_root_breadcrumbs(self):
# Should stop breadcrumbs from crumbing
directlyProvides(self.folder, IContainmentRoot)
view = self.folder.unrestrictedTraverse('testoid/@@absolute_url')
expected = (
{'url': 'http://nohost/test_folder_1_', 'name': 'test_folder_1_'},
{'url': 'http://nohost/test_folder_1_/testoid', 'name': 'testoid'})
self.assertEquals(expected, view.breadcrumbs())
def test_standard_macros(self):
view = self.folder.unrestrictedTraverse('testoid/@@fivetest_macros')
self.assertRaises(KeyError, view.__getitem__, 'non-existing-macro')
self.failUnless(view['birdmacro'])
self.failUnless(view['dogmacro'])
# Test aliases
self.failUnless(view['flying'])
self.failUnless(view['walking'])
self.assertEquals(view['flying'], view['birdmacro'])
self.assertEquals(view['walking'], view['dogmacro'])
# Test traversal
base = 'testoid/@@fivetest_macros/%s'
for macro in ('birdmacro', 'dogmacro',
'flying', 'walking'):
view = self.folder.unrestrictedTraverse(base % macro)
self.failUnless(view)
def test_unrestrictedTraverse_non_existing(self):
self.assertRaises(AttributeError, self.folder.unrestrictedTraverse,
'testoid/@@invalid_page')
def test_get_widgets_for_schema_fields(self):
salutation = Choice(title=u'Salutation', values=("Mr.", "Mrs.", "Captain", "Don"))
contactname = TextLine(title=u'Name')
request = FakeRequest()
salutation = salutation.bind(request)
contactname = contactname.bind(request)
view1 = getViewProviding(contactname, IInputWidget, request)
self.assertEquals(view1.__class__, zope.app.form.browser.textwidgets.TextWidget)
view2 = getViewProviding(salutation, IInputWidget, request)
self.assertEquals(view2.__class__, zope.app.form.browser.itemswidgets.DropdownWidget)
# Disabled __call__ overriding for now. Causes more trouble
# than it fixes.
# def test_existing_call(self):
# view = self.folder.unrestrictedTraverse('testcall')
# self.assertEquals("Default __call__ called", view())
class PublishTest(Functional, FiveTestCase):
"""Test a few publishing features"""
def afterSetUp(self):
manage_addSimpleContent(self.folder, 'testoid', 'Testoid')
manage_addCallableSimpleContent(self.folder, 'testcall', 'TestCall')
manage_addIndexSimpleContent(self.folder, 'testindex', 'TestIndex')
uf = self.folder.acl_users
uf._doAddUser('viewer', 'secret', [], [])
uf._doAddUser('manager', 'r00t', ['Manager'], [])
def test_no_doc_string(self):
for view_name in ['nodoc-function', 'nodoc-method', 'nodoc-object']:
response = self.publish('/test_folder_1_/testoid/%s' % view_name)
self.assertEquals("No docstring", response.getBody())
def test_fallback_raises_notfound(self):
# If we return None in __fallback_traverse__, this test passes
# but for the wrong reason: None doesn't have a docstring so
# BaseRequest raises NotFoundError. A functional test would be
# perfect here.
response = self.publish('/test_folder_1_/testoid/doesntexist')
self.assertEquals(404, response.getStatus())
def test_existing_bobo_traverse(self):
manage_addFancyContent(self.folder, 'fancy', '')
# check if the old bobo_traverse method can still kick in
response = self.publish('/test_folder_1_/fancy/something-else')
self.assertEquals('something-else', response.getBody())
# check if z3-based view lookup works
response = self.publish('/test_folder_1_/fancy/fancy')
self.assertEquals("Fancy, fancy", response.getBody())
def test_publish_image_resource(self):
url = '/test_folder_1_/testoid/++resource++pattern.png'
response = self.publish(url, basic='manager:r00t')
self.assertEquals(200, response.getStatus())
def test_publish_file_resource(self):
url = '/test_folder_1_/testoid/++resource++style.css'
response = self.publish(url, basic='manager:r00t')
self.assertEquals(200, response.getStatus())
# Disabled __call__ overriding for now. Causes more trouble
# than it fixes.
# def test_existing_call(self):
# response = self.publish('/test_folder_1_/testcall')
# self.assertEquals("Default __call__ called", response.getBody())
def test_existing_index(self):
response = self.publish('/test_folder_1_/testindex')
self.assertEquals("Default index_html called", response.getBody())
def test_default_view(self):
response = self.publish('/test_folder_1_/testoid', basic='manager:r00t')
self.assertEquals("The eagle has landed", response.getBody())
def test_pages_from_directory(self):
response = self.publish('/test_folder_1_/testoid/dirpage1')
self.assert_('page 1' in response.getBody())
response = self.publish('/test_folder_1_/testoid/dirpage2')
self.assert_('page 2' in response.getBody())
class IRecurse(Interface):
pass
class Recurse(Traversable):
implements(IRecurse)
def view(self):
return self()
def __call__(self):
return 'foo'
classDefaultViewable(Recurse)
class RecursionTest(unittest.TestCase):
def setUp(self):
self.ob = Recurse()
def test_recursive_call(self):
from zope.app import zapi
from zope.publisher.interfaces.browser import IBrowserRequest
pres = zapi.getGlobalService('Presentation')
type = IBrowserRequest
pres.setDefaultViewName(IRecurse, type, 'view')
self.assertEquals(self.ob.view(), 'foo')
self.assertEquals(self.ob(), 'foo')
from zope.app.publisher.browser.globalbrowsermenuservice import \
globalBrowserMenuService
class MenuTest(FiveTestCase):
def afterSetUp(self):
manage_addIndexSimpleContent(self.folder, 'test', 'Test')
def test_menu(self):
request = FakeRequest()
# XXX not sure why we need this..
request.getURL = lambda: 'http://www.infrae.com'
menu = globalBrowserMenuService.getMenu('testmenu',
self.folder.test,
request)
self.assertEquals(3, len(menu))
# sort menu items by title so we get a stable testable result
menu.sort(lambda x, y: cmp(x['title'], y['title']))
self.assertEquals('Test Menu Item', menu[0]['title'])
self.assertEquals('seagull.html', menu[0]['action'])
self.assertEquals('Test Menu Item 2', menu[1]['title'])
self.assertEquals('parakeet.html', menu[1]['action'])
def test_suite():
from unittest import TestSuite, makeSuite
suite = TestSuite()
suite.addTest(makeSuite(RecursionTest))
suite.addTest(makeSuite(FiveTest))
suite.addTest(makeSuite(PublishTest))
suite.addTest(makeSuite(MenuTest))
return suite
if __name__ == '__main__':
framework()
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
from Products.Five.tests.fivetest import *
from zope.component import getView
from zope.testing.cleanup import CleanUp
from Products.Five import zcml
from Products.Five.traversable import FakeRequest
from Products.Five.security import clearSecurityInfo, checkPermission
from Products.Five.tests.dummy import Dummy1, Dummy2
from Globals import InitializeClass
class PageSecurityTest(CleanUp, FiveTestCase):
def setUp(self):
super(PageSecurityTest, self).setUp()
zcml.reset()
zcml.load_site()
self.dummy1 = Dummy1
def tearDown(self):
super(PageSecurityTest, self).tearDown()
zcml.reset()
clearSecurityInfo(self.dummy1)
def test_page_security(self):
self.failIf(hasattr(self.dummy1, '__ac_permissions__'))
decl = """
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser">
<browser:page
for="Products.Five.tests.dummy.IDummy"
class="Products.Five.tests.dummy.DummyView"
attribute="foo"
name="foo.txt"
permission="zope2.ViewManagementScreens"
/>
</configure>
"""
zcml.string(decl)
request = FakeRequest()
view = getView(Dummy1(), 'foo.txt', request)
ac = getattr(view, '__ac_permissions__')
ex_ac = (('View management screens', ('foo',)),)
self.assertEquals(ac, ex_ac)
foo_roles = getattr(view, 'foo__roles__', None)
self.failIf(foo_roles is None)
self.failIf(foo_roles == ())
self.assertEquals(foo_roles.__of__(view), ('Manager',))
class SecurityEquivalenceTest(CleanUp, FiveTestCase):
def setUp(self):
super(SecurityEquivalenceTest, self).setUp()
zcml.reset()
zcml.initialize()
self.dummy1 = Dummy1
self.dummy2 = Dummy2
def tearDown(self):
zcml.reset()
super(SecurityEquivalenceTest, self).tearDown()
clearSecurityInfo(self.dummy1)
clearSecurityInfo(self.dummy2)
def test_equivalence(self):
self.failIf(hasattr(self.dummy1, '__ac_permissions__'))
self.failIf(hasattr(self.dummy2, '__ac_permissions__'))
decl = """
<configure xmlns="http://namespaces.zope.org/zope">
<content
class="Products.Five.tests.dummy.Dummy1">
<allow attributes="foo" />
<!-- XXX not yet supported
<deny attributes="baz" />
-->
<require attributes="bar keg"
permission="zope2.ViewManagementScreens"
/>
</content>
</configure>
"""
zcml.string(decl)
InitializeClass(self.dummy2)
ac1 = getattr(self.dummy1, '__ac_permissions__')
ac2 = getattr(self.dummy2, '__ac_permissions__')
self.assertEquals(ac1, ac2)
bar_roles1 = getattr(self.dummy1, 'bar__roles__').__of__(self.dummy1)
self.assertEquals(bar_roles1.__of__(self.dummy1), ('Manager',))
keg_roles1 = getattr(self.dummy1, 'keg__roles__').__of__(self.dummy1)
self.assertEquals(keg_roles1.__of__(self.dummy1), ('Manager',))
foo_roles1 = getattr(self.dummy1, 'foo__roles__')
self.assertEquals(foo_roles1, None)
# XXX Not yet supported.
# baz_roles1 = getattr(self.dummy1, 'baz__roles__')
# self.assertEquals(baz_roles1, ())
bar_roles2 = getattr(self.dummy2, 'bar__roles__').__of__(self.dummy2)
self.assertEquals(bar_roles2.__of__(self.dummy2), ('Manager',))
keg_roles2 = getattr(self.dummy2, 'keg__roles__').__of__(self.dummy2)
self.assertEquals(keg_roles2.__of__(self.dummy2), ('Manager',))
foo_roles2 = getattr(self.dummy2, 'foo__roles__')
self.assertEquals(foo_roles2, None)
baz_roles2 = getattr(self.dummy2, 'baz__roles__')
self.assertEquals(baz_roles2, ())
class CheckPermissionTest(FiveTestCase):
def test_publicPermissionId(self):
#import pdb;pdb.set_trace()
self.failUnless(checkPermission('zope2.Public', self.folder))
def test_privatePermissionId(self):
self.failIf(checkPermission('zope.Private', self.folder))
self.failIf(checkPermission('zope2.Private', self.folder))
def test_accessPermissionId(self):
self.failUnless(checkPermission('zope2.AccessContentsInformation', self.folder))
def test_invalidPermissionId(self):
self.failIf(checkPermission('notapermission', self.folder))
def test_suite():
from unittest import TestSuite, makeSuite
suite = TestSuite()
#suite.addTest(makeSuite(SecurityEquivalenceTest))
#suite.addTest(makeSuite(PageSecurityTest))
suite.addTest(makeSuite(CheckPermissionTest))
return suite
if __name__ == '__main__':
framework()
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
from Products.Five.tests.fivetest import *
from AccessControl import getSecurityManager
from AccessControl import Unauthorized
import glob
from Products.Five.tests.products import FiveTest
_prefix = os.path.dirname(FiveTest.__file__)
dir_resource_names = [os.path.basename(r)
for r in (glob.glob('%s/*.png' % _prefix) +
glob.glob('%s/*.pt' % _prefix) +
glob.glob('%s/[a-z]*.py' % _prefix) +
glob.glob('%s/*.css' % _prefix))]
ViewManagementScreens = 'View management screens'
from Products.Five.tests.products.FiveTest.simplecontent import manage_addSimpleContent
class RestrictedPythonTest(FiveTestCase):
"""
Test whether code is really restricted
Kind permission from Plone to use this.
"""
def addPS(self, id, params='', body=''):
# clean up any 'ps' that's already here..
try:
self.folder._getOb(id)
self.folder.manage_delObjects([id])
except AttributeError:
pass # it's okay, no 'ps' exists yet
factory = self.folder.manage_addProduct['PythonScripts']
factory.manage_addPythonScript(id)
self.folder[id].ZPythonScript_edit(params, body)
def check(self, psbody):
self.addPS('ps', body=psbody)
try:
self.folder.ps()
except (ImportError, Unauthorized), e:
self.fail(e)
def checkUnauthorized(self, psbody):
self.addPS('ps', body=psbody)
try:
self.folder.ps()
except (AttributeError, Unauthorized):
pass
else:
self.fail("Authorized but shouldn't be")
view_names = [
'eagle.txt',
'falcon.html',
'owl.html',
'flamingo.html',
'flamingo2.html',
'condor.html']
public_view_names = [
'public_attribute_page',
'public_template_page',
'public_template_class_page']
resource_names = [
'cockatiel.html',
'style.css',
'pattern.png'
]
class SecurityTest(RestrictedPythonTest):
def afterSetUp(self):
manage_addSimpleContent(self.folder, 'testoid', 'Testoid')
uf = self.folder.acl_users
uf._doAddUser('viewer', 'secret', [], [])
uf._doAddUser('manager', 'r00t', ['Manager'], [])
def test_no_permission(self):
self.login('viewer')
for view_name in view_names:
self.checkUnauthorized(
'context.restrictedTraverse("testoid/%s")()' % view_name)
def test_resource_no_permission(self):
self.login('viewer')
for resource in resource_names:
self.checkUnauthorized(
'context.restrictedTraverse("testoid/++resource++%s")()' %
resource)
def test_directory_resource_no_permission(self):
self.login('viewer')
base = 'testoid/++resource++fivetest_resources/%s'
for resource in dir_resource_names:
path = base % resource
self.checkUnauthorized(
'context.restrictedTraverse("%s")' % path)
def test_permission(self):
self.login('manager')
for view_name in view_names:
self.check(
'context.restrictedTraverse("testoid/%s")()' % view_name)
def test_resource_permission(self):
self.login('manager')
for resource in resource_names:
self.check(
'context.restrictedTraverse("testoid/++resource++%s")()' %
resource)
def test_directory_resource_permission(self):
self.login('manager')
base = 'testoid/++resource++fivetest_resources/%s'
for resource in dir_resource_names:
path = base % resource
self.check(
'context.restrictedTraverse("%s")' % path)
def test_public_permission(self):
self.logout()
for view_name in public_view_names:
self.check(
'context.restrictedTraverse("testoid/%s")()' % view_name)
def test_view_method_permission(self):
self.login('manager')
self.check(
'context.restrictedTraverse("testoid/eagle.method").eagle()')
class PublishTest(Functional, FiveTestCase):
"""A functional test for security actually involving the publisher.
"""
def afterSetUp(self):
manage_addSimpleContent(self.folder, 'testoid', 'Testoid')
uf = self.folder.acl_users
uf._doAddUser('viewer', 'secret', [], [])
uf._doAddUser('manager', 'r00t', ['Manager'], [])
def test_no_permission(self):
for view_name in view_names:
response = self.publish('/test_folder_1_/testoid/%s' % view_name,
basic='viewer:secret')
# we expect that we get a 401 Unauthorized
self.assertEqual(response.getStatus(), 401)
def test_permission(self):
for view_name in view_names:
response = self.publish('/test_folder_1_/testoid/%s' % view_name,
basic='manager:r00t')
# we expect that we get a 200 Ok
self.assertEqual(response.getStatus(), 200)
def test_public_permission(self):
for view_name in public_view_names:
response = self.publish('/test_folder_1_/testoid/%s' % view_name)
self.assertEqual(response.getStatus(), 200)
def test_suite():
from unittest import TestSuite, makeSuite
suite = TestSuite()
suite.addTest(makeSuite(SecurityTest))
suite.addTest(makeSuite(PublishTest))
return suite
if __name__ == '__main__':
framework()
import os
from os.path import join, abspath, dirname, split, exists
def process():
"""Read in zope.conf configuration file.
This is a hack but there doesn't seem to be a better way.
"""
_prefix = os.environ.get('INSTANCE_HOME')
if not _prefix:
try:
__file__
except NameError:
# Test was called directly, so no __file__ global exists.
_prefix = abspath(os.curdir)
else:
# Test was called by another test.
_prefix = abspath(dirname(__file__))
_prefix = join(_prefix, '..', '..', '..')
_config = join(_prefix, 'etc', 'zope.conf')
if exists(_config):
from Zope2 import configure
configure(_config)
##############################################################################
#
# Copyright (c) 2004 Five Contributors. All rights reserved.
#
# This software is distributed under the terms of the Zope Public
# License (ZPL) v2.1. See COPYING.txt for more information.
#
##############################################################################
"""Machinery for making things traversable through adaptation
$Id: traversable.py 9786 2005-03-15 12:53:39Z efge $
"""
from zExceptions import NotFound
from zope.exceptions import NotFoundError
from zope.component import getView, ComponentLookupError
from zope.interface import implements
from zope.publisher.interfaces.browser import IBrowserRequest
from zope.app.traversing.interfaces import ITraverser, ITraversable
from zope.app.traversing.adapters import DefaultTraversable
from zope.app.traversing.adapters import traversePathElement
from zope.security.management import thread_local
from AccessControl import getSecurityManager
_marker = object
class FakeRequest:
implements(IBrowserRequest)
def getPresentationSkin(self):
return None
def has_key(self, key):
return False
def newInteraction():
"""Con Zope 3 to use Zope 2's checkPermission.
Zope 3 when it does a checkPermission will turn around and
ask the thread local interaction for the checkPermission method.
By making the interaction *be* Zope 2's security manager, we can
con Zope 3 into using Zope 2's checker...
"""
if getattr(thread_local, 'interaction', None) is None:
thread_local.interaction = getSecurityManager()
class Traversable:
"""A mixin to make an object traversable using an ITraverser adapter.
"""
__five_traversable__ = True
def __fallback_traverse__(self, REQUEST, name):
"""Method hook for fallback traversal
This method is called by __bobo_traverse___ when Zope3-style
ITraverser traversal fails.
Just raise a AttributeError to indicate traversal has failed
and let Zope do it's job.
"""
raise AttributeError, name
def __bobo_traverse__(self, REQUEST, name):
"""Hook for Zope 2 traversal
This method is called by Zope 2's ZPublisher upon traversal.
It allows us to trick it into faking the Zope 3 traversal system
by using an ITraverser adapter.
"""
if not IBrowserRequest.providedBy(REQUEST):
# Try to get the REQUEST by acquisition
REQUEST = getattr(self, 'REQUEST', None)
if not IBrowserRequest.providedBy(REQUEST):
REQUEST = FakeRequest()
# con Zope 3 into using Zope 2's checkPermission
newInteraction()
try:
return ITraverser(self).traverse(
path=[name], request=REQUEST).__of__(self)
except (ComponentLookupError, NotFoundError,
AttributeError, KeyError, NotFound):
pass
try:
return getattr(self, name)
except AttributeError:
pass
try:
return self[name]
except (AttributeError, KeyError):
pass
return self.__fallback_traverse__(REQUEST, name)
__bobo_traverse__.__five_method__ = True
class FiveTraversable(DefaultTraversable):
def traverse(self, name, furtherPath):
context = self._subject
__traceback_info__ = (context, name, furtherPath)
# Find the REQUEST
REQUEST = getattr(context, 'REQUEST', None)
if not IBrowserRequest.providedBy(REQUEST):
REQUEST = FakeRequest()
# Try to lookup a view first
try:
return getView(context, name, REQUEST)
except ComponentLookupError:
pass
# If a view can't be found, then use default traversable
return super(FiveTraversable, self).traverse(name, furtherPath)
##############################################################################
#
# Copyright (c) 2004 Five Contributors. All rights reserved.
#
# This software is distributed under the terms of the Zope Public
# License (ZPL) v2.1. See COPYING.txt for more information.
#
##############################################################################
"""Machinery for making things viewable
$Id: traversable.py 5763 2004-07-28 20:15:11Z dreamcatcher $
"""
import inspect
from zExceptions import NotFound
from zope.exceptions import NotFoundError
from zope.component import getView, getDefaultViewName, ComponentLookupError
from zope.interface import implements
from zope.publisher.interfaces.browser import IBrowserRequest
from traversable import FakeRequest
from interfaces import IBrowserDefault
_marker = object
class Viewable:
"""A mixin to make an object viewable.
"""
__five_viewable__ = True
def __fallback_default__(self, request):
"""Try to dispatch to existing index_html or __call__"""
if getattr(self, 'index_html', None):
return self, ('index_html',)
if getattr(self, 'fallback_call__', None):
return self, ('fallback_call__',)
# XXX Should never get this far. But if it does?
# def fallback_call__(self, *args, **kw):
# """By default, return self"""
# return self
# we have a default view, tell zpublisher to go there
def __browser_default__(self, request):
obj = self
path = None
try:
obj, path = IBrowserDefault(self).defaultView(request)
except ComponentLookupError:
pass
if path:
if len(path) == 1 and path[0] == '__call__':
return obj, ('fallback_call__',)
return obj, path
return self.__fallback_default__(request)
__browser_default__.__five_method__ = True
# this is technically not needed because ZPublisher finds our
# attribute through __browser_default__; but we also want to be
# able to call pages from python modules, PythonScripts or ZPT
# def __call__(self, *args, **kw):
# """ """
# request = kw.get('REQUEST')
# if not IBrowserRequest.providedBy(request):
# request = getattr(self, 'REQUEST', None)
# if not IBrowserRequest.providedBy(request):
# request = FakeRequest()
# obj, path = self.__browser_default__(request)
# if path and not simpleRecursion():
# meth = obj.unrestrictedTraverse(path)
# if meth is not None:
# return meth(*args, **kw)
# return self.fallback_call__(*args, **kw)
# __call__.__five_method__ = True
# def simpleRecursion():
# # This tests for simple recursion, which can easily happen
# # in CMF, like the following:
# # - Object has a method named 'view'
# # - 'view' method calls '__call__'
# # - five:viewable overrides call to use '__browser_default__'
# # to find a default view and call it
# # - defaultView is set to 'view'
# # Bang. Infinite recursion.
# stack = inspect.stack()
# try:
# if len(stack) < 4:
# return False
# if stack[2][1:4] == stack[4][1:4]:
# return True
# finally:
# del stack
# return False
class BrowserDefault(object):
implements(IBrowserDefault)
def __init__(self, context):
self.context = context
def defaultView(self, request):
context = self.context
name = None
try:
name = getDefaultViewName(context, request)
except ComponentLookupError:
pass
return context, [name,]
##############################################################################
#
# Copyright (c) 2004 Five Contributors. All rights reserved.
#
# This software is distributed under the terms of the Zope Public
# License (ZPL) v2.1. See COPYING.txt for more information.
#
##############################################################################
"""ZCML machinery
$Id: zcml.py 9855 2005-03-17 16:41:09Z shh $
"""
import os
from zope.configuration import xmlconfig
_initialized = False
_context = None
def load_site():
"""Load the appropriate ZCML file.
Note that this can be called multiple times, unlike in Zope 3. This
is needed because in Zope 2 we don't (yet) have a master ZCML file
which can include all the others.
"""
global _initialized
if _initialized:
return
_initialized = True
# load instance site configuration file
site_zcml = os.path.join(INSTANCE_HOME, "etc", "site.zcml")
if os.path.exists(site_zcml):
file = site_zcml
else:
file = os.path.join(os.path.dirname(__file__), "skel", "site.zcml")
global _context
_context = xmlconfig.file(file)
def load_config(file, package=None):
"""Load an additional ZCML file into the context.
Use with extreme care.
"""
global _context
_context = xmlconfig.file(file, package, _context)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment