Prepare to stitch in Five via svn:externals

parent d9455390
This diff is collapsed.
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.
- 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.
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 (tseaver@palladion.com)
- Jan-Wijbrand Kolman (jw@infrae.com)
- Stefan Holek (ssh@epy.co.at)
- Florent Guillaume (fg@nuxeo.com)
- Godefroid Chapelle (gotcha@bubblenet.be)
- Andy Adiwidjaja (mail@adiwidjaja.com)
- Stuart Bishop (stuart@stuartbishop.net)
- Simon Eisenmann (simon@struktur.de)
- Dieter Maurer (dieter@handshake.de)
- Yvo Schubbe (y.2005-@wcm-solutions.de)
- Malcolm Cleaton (malcolm@jamkit.com)
- Tarek Ziad (tziade@nuxeo.com)
- Whit Morriss (whit@longnow.org)
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 1.3
-------------------------
* Zope 2.9+ with Python 2.4.1+
Note that Five 1.3 is already part of Zope 2.9. You can still install
a newer Five version in your instance, if you like. It will override
the Five product inside the Zope tree.
Compatability matrix
--------------------
The following table shows which Five version can and should be used
with which Zope 2 and Zope 3 versions.
============ ======================= =========== ========
. Zope 2.7 Zope 2.8 Zope 2.9
------------ ----------------------- ----------- --------
. Zope X3 3.0 (not incl.) Zope X3 3.0 Zope 3.2
============ ======================= =========== ========
Five 1.0 X included
Five 1.1[#]_ X X
Five 1.2 X
Five 1.3 included
============ ======================= =========== ========
.. [#] This branch is no longer actively maintained.
Running 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, 2005 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.
#
##############################################################################
"""Initialize the Five product
$Id: __init__.py 20254 2005-11-25 18:45:08Z 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
from skin.standardmacros import StandardMacros
def initialize(context):
zcml.load_site()
##############################################################################
#
# Copyright (c) 2004, 2005 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.
#
##############################################################################
""" Z2 -> Z3 bridge utilities.
$Id: bridge.py 14504 2005-07-11 15:49:59Z yuppie $
"""
from Interface._InterfaceClass import Interface as Z2_InterfaceClass
from Interface import Interface as Z2_Interface
from Interface import Attribute as Z2_Attribute
from Interface.Method import Method as Z2_Method
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
from zope.interface.interface import Method as Z3_Method
_bridges = {Z2_Interface: Z3_Interface}
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 in _bridges:
return _bridges[z2i]
name = z2i.getName()
bases = [ fromZ2Interface(x) for x in z2i.getBases() ]
attrs = {}
for k, v in z2i.namesAndDescriptions():
if isinstance(v, Z2_Method):
v = fromZ2Method(v)
elif 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.
z3i = Z3_InterfaceClass(name=name,
bases=tuple(bases),
attrs=attrs,
__doc__=z2i.getDoc(),
__module__=z2i.__module__)
_bridges[z2i] = z3i
return z3i
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())
def fromZ2Method(z2m):
""" Return a Zope 3 interface method corresponding to 'z2m'.
o 'z2m' must be a Zope 2 interface method.
"""
if not isinstance(z2m, Z2_Method):
raise ValueError, 'Not a Zope 2 interface method!'
z3m = Z3_Method(z2m.getName(), z2m.getDoc())
sig = z2m.getSignatureInfo()
z3m.positional = sig['positional']
z3m.required = sig['required']
z3m.optional = sig['optional']
z3m.varargs = sig['varargs']
z3m.kwargs = sig['kwargs']
return z3m
##############################################################################
#
# Copyright (c) 2004, 2005 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.
#
##############################################################################
"""Utils to be reused
$Id: ReuseUtils.py 12907 2005-05-31 06:26:15Z philikon $
"""
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
##############################################################################
#
# Copyright (c) 2004, 2005 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.
#
##############################################################################
"""Trusted expression
$Id: TrustedExpression.py 17373 2005-09-08 14:48:27Z regebro $
"""
from sys import modules
from Products.PageTemplates.PythonExpr import PythonExpr
from Products.PageTemplates.Expressions import \
SubPathExpr, PathExpr, \
StringExpr, \
getEngine, installHandlers,\
SecureModuleImporter
from ReuseUtils import rebindFunction
ModuleImporter = SecureModuleImporter
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, TypeError): # 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, 2005 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.
#
##############################################################################
"""Provide basic browser functionality
$Id: __init__.py 19283 2005-10-31 17:43:51Z philikon $
"""
import Acquisition
import zope.app.publisher.browser
class BrowserView(Acquisition.Explicit, zope.app.publisher.browser.BrowserView):
"""Five browser view
Mixes in explicit acquisition so that security can be acquired for
views"""
##############################################################################
#
# Copyright (c) 2004, 2005 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.
#
##############################################################################
"""Absolute URL
$Id: absoluteurl.py 13254 2005-06-09 21:40:41Z philikon $
"""
from Acquisition import aq_inner, aq_parent
from OFS.interfaces import ITraversable
from zope.interface import implements
from zope.app import zapi
from zope.app.traversing.browser.interfaces import IAbsoluteURL
from Products.Five.browser import BrowserView
class AbsoluteURL(BrowserView):
"""An adapter for Zope3-style absolute_url using Zope2 methods
(original: zope.app.traversing.browser.absoluteurl)
"""
implements(IAbsoluteURL)
def __init__(self, context, request):
self.context, self.request = context, request
def __str__(self):
context = aq_inner(self.context)
return context.absolute_url()
__call__ = __str__
def breadcrumbs(self):
context = aq_inner(self.context)
container = aq_parent(context)
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 = zapi.getMultiAdapter((container, request), IAbsoluteURL)
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 = aq_inner(self.context)
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()
},)
<html metal:use-macro="context/@@standard_macros/page">
<body>
<div metal:fill-slot="main">
<p>+ screen not yet supported by Five</p>
</div>
</body>
</html>
##############################################################################
#
# Copyright (c) 2002-2005 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.publisher.browser.menu import getMenu
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.browser.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)
else:
# Invoke the name chooser even when we have a
# name. It'll do useful things with it like converting
# the incoming unicode to an ASCII string.
name = chooser.chooseName(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.getMultiAdapter((self.context, self.request),
name=u"absolute_url")) + '/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.getMultiAdapter((self, request), name=view_name)
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.getMultiAdapter((self, self.request), name=u"absolute_url"),
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
result = []
for menu_id in (self.menu_id, 'zope.app.container.add'):
if not menu_id:
continue
for item in 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):
# ObjectManager can only deal with ASCII names. Specially
# ObjectManager._checkId can only deal with strings.
try:
name = name.encode('ascii')
except UnicodeDecodeError:
raise UserError, "Id must contain only ASCII characters."
try:
self.context._checkId(name, allow_dup=False)
except BadRequest, e:
msg = ' '.join(e.args) or "Id is in use or invalid"
raise UserError, msg
def chooseName(self, name, object):
if not name:
name = object.__class__.__name__
else:
try:
name = name.encode('ascii')
except UnicodeDecodeError:
raise UserError, "Id must contain only ASCII characters."
dot = name.rfind('.')
if dot >= 0:
suffix = name[dot:]
name = name[:dot]
else:
suffix = ''
n = name + suffix
i = 0
while True:
i += 1
try:
self.context._getOb(n)
except AttributeError:
break
n = name + '-' + str(i) + suffix
# Make sure the name is valid. We may have started with
# something bad.
self.checkName(n, object)
return n
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser">
<browser:defaultView name="index.html" />
<interface
interface="zope.publisher.interfaces.browser.ILayer"
/>
<interface
interface="zope.publisher.interfaces.browser.ISkin"
/>
<interface
interface="zope.app.publisher.interfaces.browser.IMenuItemType"
/>
<browser:layer
name="default"
interface="zope.publisher.interfaces.browser.IDefaultBrowserLayer"
/>
<browser:page
for="*"
name="absolute_url"
class=".absoluteurl.AbsoluteURL"
permission="zope.Public"
allowed_interface="zope.app.traversing.browser.interfaces.IAbsoluteURL"
/>
<view
for="*"
factory=".absoluteurl.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=".absoluteurl.SiteAbsoluteURL"
permission="zope.Public"
allowed_interface="zope.app.traversing.browser.interfaces.IAbsoluteURL"
/>
<view
for="zope.app.traversing.interfaces.IContainmentRoot"
factory=".absoluteurl.SiteAbsoluteURL"
type="zope.publisher.interfaces.http.IHTTPRequest"
permission="zope.Public"
provides="zope.app.traversing.browser.interfaces.IAbsoluteURL"
/>
<browser:view
for="OFS.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="OFS.interfaces.IObjectManager"
factory=".adding.ObjectManagerNameChooser"
provides="zope.app.container.interfaces.INameChooser"
/>
<!-- Menu access -->
<browser:page
for="*"
name="view_get_menu"
permission="zope.Public"
class=".menu.MenuAccessView"
allowed_interface="zope.app.publisher.interfaces.browser.IMenuAccessView"
/>
</configure>
##############################################################################
#
# Copyright (c) 2005 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.
#
##############################################################################
"""Some menu code
$Id: menu.py 19283 2005-10-31 17:43:51Z philikon $
"""
from zope.interface import implements
from zope.app.publisher.interfaces.browser import IMenuAccessView
from zope.app.publisher.browser.menu import getMenu
from Products.Five import BrowserView
class MenuAccessView(BrowserView):
implements(IMenuAccessView)
def __getitem__(self, menu_id):
return getMenu(menu_id, self.context, self.request)
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:meta="http://namespaces.zope.org/meta">
<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="zope.app.publisher.browser.metaconfigure.defaultView"
/>
<meta:directive
name="page"
schema="zope.app.publisher.browser.metadirectives.IPageDirective"
handler=".metaconfigure.page"
/>
<meta:complexDirective
name="pages"
schema="zope.app.publisher.browser.metadirectives.IPagesDirective"
handler=".metaconfigure.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=".metaconfigure.resource"
/>
<meta:directive
name="resourceDirectory"
schema="zope.app.publisher.browser.metadirectives.IResourceDirectoryDirective"
handler=".metaconfigure.resourceDirectory"
/>
<meta:directive
name="menu"
schema="zope.app.publisher.browser.metadirectives.IMenuDirective"
handler="zope.app.publisher.browser.menumeta.menuDirective"
/>
<meta:directive
name="menuItem"
schema="zope.app.publisher.browser.metadirectives.IMenuItemDirective"
handler="zope.app.publisher.browser.menumeta.menuItemDirective"
/>
<meta:complexDirective
name="menuItems"
schema="zope.app.publisher.browser.metadirectives.IMenuItemsDirective"
handler="zope.app.publisher.browser.menumeta.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=".metaconfigure.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:directives>
</configure>
This diff is collapsed.
##############################################################################
#
# Copyright (c) 2004, 2005 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.
#
##############################################################################
"""A 'PageTemplateFile' without security restrictions.
$Id: pagetemplatefile.py 15193 2005-07-27 13:27:04Z regebro $
"""
import os, sys
from Globals import package_home
from AccessControl import getSecurityManager
from Shared.DC.Scripts.Bindings import Unauthorized, UnauthorizedBinding
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
from zope.app.pagetemplate.viewpagetemplatefile import ViewMapper
from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
from Products.Five.browser.ReuseUtils import rebindFunction
from Products.Five.browser.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):
try:
root = self.getPhysicalRoot()
view = self._getContext()
except AttributeError:
# self has no attribute getPhysicalRoot. This typically happens
# when the template has no proper acquisition context.
# That also means it has no view. /regebro
root = self.context.getPhysicalRoot()
view = None
here = self.context.aq_inner
request = getattr(root, 'REQUEST', None)
c = {'template': self,
'here': here,
'context': here,
'container': here,
'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)
##############################################################################
#
# Copyright (c) 2004, 2005 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.
#
##############################################################################
"""Provide basic resource functionality
$Id: resource.py 13268 2005-06-10 14:18:23Z philikon $
"""
import os
import urllib
import Acquisition
from ComputedAttribute import ComputedAttribute
from OFS.Traversable import Traversable as OFSTraversable
from zope.interface import implements
from zope.component.interfaces import IResource
from zope.publisher.interfaces.browser import IBrowserPublisher
from zope.app import zapi
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
from Products.Five.browser import BrowserView
_marker = []
class Resource(Acquisition.Explicit):
"""A publishable resource
"""
implements(IResource)
def __init__(self, request):
self.request = request
def __call__(self):
name = self.__name__
container = self.__parent__
# TODO Zope 3 uses site = getSite() instead of container here
# and the @@ resource access view
url = str(zapi.getMultiAdapter((container, self.request), IAbsoluteURL))
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, ('render',)
def render(self):
"""Rendered content"""
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 __init__(self, context, request):
BrowserView.__init__(self, context, request)
# OFSTraversable.absolute_url() assumes self.REQUEST being
# accessible:
self.REQUEST = request
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 KeyError(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
============
Adding tests
============
ObjectManagerNameChooser
------------------------
First we need to import and setup some prerequisites:
>>> from Products.Five.tests.testing import manage_addFiveTraversableFolder
>>> from Products.Five.browser.adding import ObjectManagerNameChooser
>>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid')
>>> chooser = ObjectManagerNameChooser(self.folder)
Now we can start. ``INameChooser`` defines a ``checkName()`` method
that checks whether a given name is valid in the container or not.
Under the hood, ``ObjectManagerNameChooser`` calls ``_checkId()`` of
the object manager. Valid names/ids are those that aren't in use yet
and don't contain invalid characters.
>>> chooser.checkName('abc', object())
>>> chooser.checkName('testoid', object())
Traceback (most recent call last):
...
UserError: The id "testoid" is invalid - it is already in use.
>>> chooser.checkName('slash/slash', object())
Traceback (most recent call last):
...
UserError: The id "slash/slash" contains characters illegal in URLs.
``INameChooser`` also promises us a ``chooseName()`` method that
chooses a name for us in case we don't have one or that chooses a
different name in case the one we chose was invalid.
>>> chooser.chooseName('', self.folder.testoid)
'FiveTraversableFolder'
>>> chooser.chooseName('abc', self.folder.testoid)
'abc'
>>> chooser.chooseName('testoid', self.folder.testoid)
'testoid-1'
Of course, if we start out with something bad, it isn't going to
become good automagically:
>>> chooser.chooseName('slash/slash', object())
Traceback (most recent call last):
...
UserError: The id "slash/slash" contains characters illegal in URLs.
<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>
<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 context/@@flamingo.html">Gamma</div>
\ No newline at end of file
<html>
<body>
<!-- fivetest is a Zope 3 style i18n domain, default is a Localizer domain -->
<p i18n:domain="fivetest" i18n:translate="">This is a message</p>
<p i18n:domain="default" i18n:translate="">Object actions</p>
</body>
</html>
##############################################################################
#
# Copyright (c) 2005 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.
#
##############################################################################
"""Test the Localizer language integration for CPS. This test
requires a full blown CPS installation to run. It is therefore
prefixed with ``cps_`` so it won't be picked up by the test runner.
$Id: cps_test_localizer.py 14400 2005-07-07 17:55:08Z philikon $
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
def test_suite():
from Testing.ZopeTestCase import installProduct, FunctionalDocFileSuite
installProduct('Five')
installProduct('BTreeFolder2')
installProduct('CMFCalendar')
installProduct('CMFCore')
installProduct('CMFDefault')
installProduct('CMFTopic')
installProduct('DCWorkflow')
installProduct('Localizer')
installProduct('MailHost')
installProduct('CPSCore')
installProduct('CPSDefault')
installProduct('CPSDirectory')
installProduct('CPSUserFolder')
installProduct('TranslationService')
installProduct('SiteAccess')
# these products should (and used to be) be optional, but they
# aren't right now.
installProduct('CPSForum')
installProduct('CPSSubscriptions')
installProduct('CPSNewsLetters')
installProduct('CPSSchemas')
installProduct('CPSDocument')
installProduct('PortalTransforms')
installProduct('Epoz')
# optional products, but apparently still needed...
installProduct('CPSRSS')
installProduct('CPSChat')
installProduct('CPSCalendar')
installProduct('CPSCollector')
installProduct('CPSMailBoxer')
return FunctionalDocFileSuite('cps_test_localizer.txt',
package='Products.Five.browser.tests')
if __name__ == '__main__':
framework()
Localizer languages
===================
Before we start, we need to set up a manager user to be able to create
the portal:
>>> uf = self.folder.acl_users
>>> uf._doAddUser('manager', 'r00t', ['Manager'], [])
We need to 1) configure the Zope 3 i18n message catalogs, 2) make the
CPS portal traversable, 3) register the Localizer languagees adapter
and 4) register our test page:
>>> configure_zcml = """
... <configure
... xmlns="http://namespaces.zope.org/zope"
... xmlns:browser="http://namespaces.zope.org/browser"
... xmlns:five="http://namespaces.zope.org/five"
... xmlns:i18n="http://namespaces.zope.org/i18n"
... >
... <configure package="Products.Five.tests">
... <i18n:registerTranslations directory="locales" />
... </configure>
...
... <five:traversable class="Products.CPSDefault.Portal.CPSDefaultSite" />
...
... <adapter
... for="zope.publisher.interfaces.http.IHTTPRequest"
... provides="zope.i18n.interfaces.IUserPreferredLanguages"
... factory="Products.Five.i18n.LocalizerLanguages"
... />
...
... <configure package="Products.Five.browser.tests">
... <browser:page
... for="*"
... template="cps_test_localizer.pt"
... name="cps_test_localizer.html"
... permission="zope2.View"
... />
... </configure>
... </configure>
... """
>>> from Products.Five import zcml
>>> zcml.load_string(configure_zcml)
Create a CPS portal. We print an additional line before creating it
because PortalTransforms might print stuff to stdout and doctest
doesn't allow us to ellide a first line.
>>> print "Ignore lines after me"; print http(r"""
... POST /test_folder_1_/manage_addProduct/CPSDefault/manage_addCPSDefaultSite HTTP/1.1
... Authorization: Basic manager:r00t
... Content-Length: 269
... Content-Type: application/x-www-form-urlencoded
...
... id=cps&title=CPS+Portal&description=&manager_id=manager&manager_sn=CPS+manager&manager_givenName=Manager&manager_email=root%40localhost&manager_password=root&manager_password_confirmation=root&langs_list%3Alist=en&langs_list%3Alist=fr&langs_list%3Alist=de&submit=Create""")
Ignore lines after me
...
HTTP/1.1 200 OK
...
Now for some actual testing... Our test page is a simple ZPT
translating two messages from different domains. The first domain is
a Zope 3 style one, the second one comes from Localizer.
Both systems should yield the same default language (English) when no
language is specified whatsoever:
>>> print http(r"""
... GET /test_folder_1_/cps/cps_test_localizer.html HTTP/1.1
... """)
HTTP/1.1 200 OK
...
<html>
<body>
<!-- fivetest is a Zope 3 style i18n domain, default is a Localizer domain -->
<p>This is a message</p>
<p>Object actions</p>
</body>
</html>
Both systems should honour the HTTP ``Accept-Language`` header in the
same way:
>>> print http(r"""
... GET /test_folder_1_/cps/cps_test_localizer.html HTTP/1.1
... Accept-Language: de
... """)
HTTP/1.1 200 OK
...
<html>
<body>
<!-- fivetest is a Zope 3 style i18n domain, default is a Localizer domain -->
<p>Dies ist eine Nachricht</p>
<p>Objekt Aktionen</p>
</body>
</html>
Both systems should also honour Localizer-specific ways of determining
the language, for example the ``LOCALIZER_LANGUAGE`` cookie:
>>> print http(r"""
... GET /test_folder_1_/cps/cps_test_localizer.html HTTP/1.1
... Accept-Language: de
... Cookie: LOCALIZER_LANGUAGE=en
... """)
HTTP/1.1 200 OK
...
<html>
<body>
<!-- fivetest is a Zope 3 style i18n domain, default is a Localizer domain -->
<p>This is a message</p>
<p>Object actions</p>
</body>
</html>
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser"
xmlns:five="http://namespaces.zope.org/five">
<five:defaultViewable
class="Products.Five.tests.testing.simplecontent.SimpleContent" />
<browser:defaultView
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
name="eagledefaultview.txt"
/>
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
name="eagledefaultview.txt"
class=".pages.SimpleView"
attribute="eagle"
permission="zope2.Public"
/>
<!-- this tests whether five:defaultViewable can be called on a class that
already provides __call__, such as our CallableSimpleContent -->
<five:defaultViewable
class="Products.Five.tests.testing.simplecontent.CallableSimpleContent" />
<!-- this tests whether five:defaultViewable can be called on a class that
already provides index_html, such as our IndexSimpleContent -->
<five:defaultViewable
class="Products.Five.tests.testing.simplecontent.IndexSimpleContent" />
<browser:defaultView
for="Products.Five.tests.testing.simplecontent.IIndexSimpleContent"
name="index_html"
/>
</configure>
<p>The falcon has taken flight</p>
\ No newline at end of file
<p tal:content="context/mymethod">Replaced</p>
<p tal:content="python:context.mymethod()">Replaced</p>
##############################################################################
#
# 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)
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()
<html i18n:domain="fivetest">
<body>
<p i18n:translate="">This is a message</p>
<p i18n:translate="explicit-msg">This is an explicit message</p>
<p i18n:translate="">These are <span tal:replace="python:4" i18n:name="number" /> messages</p>
<p i18n:translate="explicit-msgs">These are <span tal:replace="python:4" i18n:name="number" /> explicit messages</p>
<table summary="This is an attribute" i18n:attributes="summary">
</table>
<table summary="Explicit summary" title="Explicit title" i18n:attributes="summary explicit-summary; title explicit-title" >
</table>
</body>
</html>
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:meta="http://namespaces.zope.org/meta"
xmlns:browser="http://namespaces.zope.org/browser"
i18n_domain="fivetest">
<!-- make the zope2.Public permission work -->
<meta:redefinePermission from="zope2.Public" to="zope.Public" />
<!-- browser menu support -->
<browser:menu
id="testmenu"
title="Test menu"
/>
<browser:menuItem
for="OFS.interfaces.IFolder"
menu="testmenu"
title="Test Menu Item"
action="seagull.html"
description="This is a test menu item"
permission="zope2.Public"
/>
<browser:menuItem
for="OFS.interfaces.IFolder"
menu="testmenu"
title="Protected Test Menu Item"
action="seagull.html"
description="This is a protected test menu item"
permission="zope2.ViewManagementScreens"
/>
<browser:menuItems
for="OFS.interfaces.IFolder"
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"
/>
<menuItem
title="Protected Test Menu Item 2"
action="falcon.html"
description="This is a protected test menu item"
permission="zope2.ViewManagementScreens"
/>
</browser:menuItems>
<!-- page in a menu -->
<browser:page
for="OFS.interfaces.IFolder"
template="cockatiel.pt"
name="cockatiel_menu_public.html"
permission="zope2.Public"
title="Page in a menu (public)"
menu="testmenu"
/>
<browser:page
for="OFS.interfaces.IFolder"
template="cockatiel.pt"
name="cockatiel_menu_protected.html"
permission="zope2.ViewManagementScreens"
title="Page in a menu (protected)"
menu="testmenu"
/>
</configure>
\ No newline at end of file
<ul>
<li tal:repeat="item python:['Alpha', 'Beta', 'Gamma']" tal:content="item"/>
</ul>
<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="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView"
attribute="mouse"
name="overridden_view"
permission="zope2.Public"
/>
</configure>
<p tal:content="python:1+1">Some content</p>
\ No newline at end of file
##############################################################################
#
# Copyright (c) 2004, 2005 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.
#
##############################################################################
"""Test browser pages
$Id: pages.py 12884 2005-05-30 13:10:41Z philikon $
"""
from Products.Five import BrowserView
class SimpleView(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 FancyView(BrowserView):
"""Fancy, fancy stuff"""
def view(self):
return "Fancy, fancy"
class CallView(BrowserView):
def __call__(self):
return "I was __call__()'ed"
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
Test browser pages
==================
Let's register a quite large amount of test pages:
>>> import Products.Five.browser.tests
>>> from Products.Five import zcml
>>> zcml.load_config("configure.zcml", Products.Five)
>>> zcml.load_config('pages.zcml', package=Products.Five.browser.tests)
Let's add a test object that we view most of the pages off of:
>>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent
>>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid')
We also need to create a stub user account and login; otherwise we
wouldn't have all the rights to do traversal etc.:
>>> uf = self.folder.acl_users
>>> uf._doAddUser('manager', 'r00t', ['Manager'], [])
>>> self.login('manager')
Now for some actual testing...
Simple pages
------------
A browser page that is a view class's attribute (method):
>>> view = self.folder.unrestrictedTraverse('testoid/eagle.txt')
>>> view is not None
True
>>> from Products.Five.browser.tests.pages import SimpleView
>>> isinstance(view, SimpleView)
True
>>> view()
'The eagle has landed'
A browser page that is a Page Template.
>>> view = self.folder.unrestrictedTraverse('testoid/owl.html')
>>> view()
'<p>2</p>\n'
A browser page that is a PageTemplate plus a view class:
>>> view = self.folder.unrestrictedTraverse('testoid/falcon.html')
>>> isinstance(view, SimpleView)
True
>>> view()
'<p>The falcon has taken flight</p>\n'
Test pages that have been registered through the cumulative
<browser:pages> directive:
>>> view = self.folder.unrestrictedTraverse('testoid/eagle-page.txt')
>>> isinstance(view, SimpleView)
True
>>> view()
'The eagle has landed'
>>> view = self.folder.unrestrictedTraverse('testoid/mouse-page.txt')
>>> isinstance(view, SimpleView)
True
>>> view()
'The mouse has been eaten by the eagle'
Zope 2 objects always need a docstring in order to be published. Five
adds a docstring automatically if a view method doesn't have it, but
it shouldn't modify existing ones:
>>> view = self.folder.unrestrictedTraverse('testoid/eagle.txt')
>>> view.eagle.__doc__ == SimpleView.eagle.__doc__
True
Test whether new-style classes are ignored when registering browser
pages with view classes. When traversing for a non-existing view, we
should get an AttributeError:
>>> self.folder.unrestrictedTraverse('testoid/@@new_style_class')
Traceback (most recent call last):
...
AttributeError: @@new_style_class
ZPT-based browser pages
-----------------------
Test access to ``context`` from ZPTs:
>>> view = self.folder.unrestrictedTraverse('testoid/flamingo.html')
>>> print view()
<p>Hello world</p>
<p>Hello world</p>
Test macro access from ZPT pages:
>>> view = self.folder.unrestrictedTraverse('testoid/seagull.html')
>>> view()
'<html><head><title>bird macro</title></head><body>Color: gray</body></html>\n'
Test whether old-style direct traversal still works with a
five:traversable class:
>>> old_view = self.folder.unrestrictedTraverse('testoid/direct')
>>> old_view()
'Direct traversal worked'
test_zpt_things:
>>> view = self.folder.unrestrictedTraverse('testoid/condor.html')
>>> print view()
<p>Hello world</p>
<p>The eagle has landed</p>
<p>Hello world</p>
<p>Hello world</p>
Make sure that tal:repeat works in ZPT browser pages:
>>> view = self.folder.unrestrictedTraverse('testoid/ostrich.html')
>>> print view()
<ul>
<li>Alpha</li>
<li>Beta</li>
<li>Gamma</li>
</ul>
<ul>
<li>0</li>
<li>1</li>
<li>2</li>
</ul>
Test TALES traversal in ZPT pages:
>>> view = self.folder.unrestrictedTraverse('testoid/tales_traversal.html')
>>> print view()
<p>testoid</p>
<p>test_folder_1_</p>
Make sure that global template variables in ZPT pages are correct:
>>> view = self.folder.unrestrictedTraverse('testoid/template_variables.html')
>>> print view()
View is a view: True
Context is testoid: True
Contaxt.aq_parent is test_folder_1_: True
Container is context: True
Here is context: True
Nothing is None: True
Default works: True
Root is the application: True
Template is a template: True
Traverse_subpath exists and is empty: True
Request is a request: True
User is manager: True
Options exist: True
Attrs exist: True
Repeat exists: True
Loop exists: True
Modules exists: True
Make sure that ZPT's aren't a security-less zone. Let's logout and
try to access some protected stuff. Let's not forgot to login again,
of course:
>>> from AccessControl import allow_module
>>> allow_module('smtpd')
>>> self.logout()
>>> view = self.folder.unrestrictedTraverse('testoid/security.html')
>>> print view()
<div>NoneType</div>
<div>smtpd</div>
>>> self.login('manager')
Test pages registered through the <five:pagesFromDirectory /> directive:
>>> view = self.folder.unrestrictedTraverse('testoid/dirpage1')
>>> print view()
<html>
<p>This is page 1</p>
</html>
>>> view = self.folder.unrestrictedTraverse('testoid/dirpage2')
>>> print view()
<html>
<p>This is page 2</p>
</html>
Low-level security
------------------
This tests security on a low level (functional pages test has
high-level security tests). Let's manually look up a protected view:
>>> from Products.Five.traversable import FakeRequest
>>> from zope.app import zapi
>>> from zope.app.publication.browser import setDefaultSkin
>>> request = FakeRequest()
>>> setDefaultSkin(request)
>>> view = zapi.getMultiAdapter((self.folder.testoid, request), name=u'eagle.txt')
It's protecting the object with the permission, and not the attribute,
so we get ('',) instead of ('eagle',):
>>> getattr(view, '__ac_permissions__')
(('View management screens', ('',)),)
Wrap into an acquisition so that imPermissionRole objects can be
evaluated. __roles__ is a imPermissionRole object:
>>> view = view.__of__(self.folder.testoid)
>>> view_roles = getattr(view, '__roles__', None)
>>> view_roles
('Manager',)
Check to see if view's context properly acquires its true
parent
>>> from Acquisition import aq_parent, aq_base, aq_inner
>>> context = getattr(view, 'context')
Check the wrapper type
>>> from Acquisition import ImplicitAcquisitionWrapper
>>> type(context) == ImplicitAcquisitionWrapper
True
The acquired parent is the view. This isn't
usually what you want.
>>> aq_parent(context) == view
True
To get what you usually want, do this
>>> context.aq_inner.aq_parent
<Folder at /test_folder_1_>
C methods work the same
>>> aq_parent(aq_inner(context))
<Folder at /test_folder_1_>
High-level security
-------------------
>>> protected_view_names = [
... 'eagle.txt', 'falcon.html', 'owl.html', 'flamingo.html',
... 'condor.html', 'protectededitform.html']
>>>
>>> public_view_names = [
... 'public_attribute_page',
... 'public_template_page',
... 'public_template_class_page',
... 'nodoc-method', 'nodoc-function', 'nodoc-object',
... 'dirpage1', 'dirpage2']
>>> from Products.Five.tests.testing.restricted import checkRestricted
>>> from Products.Five.tests.testing.restricted import checkUnauthorized
As long as we're not authenticated, we should get Unauthorized for
protected views, but we should be able to view the public ones:
>>> self.logout()
>>> for view_name in protected_view_names:
... checkUnauthorized(
... self.folder,
... 'context.restrictedTraverse("testoid/%s")()' % view_name)
>>> for view_name in public_view_names:
... checkRestricted(
... self.folder,
... 'context.restrictedTraverse("testoid/%s")()' % view_name)
>>> self.login('manager')
Being logged in as a manager again, we find that the protected pages
are not accessible to us:
>>> for view_name in protected_view_names:
... checkRestricted(
... self.folder,
... 'context.restrictedTraverse("testoid/%s")()' % view_name)
>>> checkRestricted(
... self.folder,
... 'context.restrictedTraverse("testoid/eagle.method").eagle()')
Other
-----
Make sure that browser pages can be overridden:
>>> zcml.load_string('''
... <includeOverrides
... package="Products.Five.browser.tests"
... file="overrides.zcml" />
... ''')
>>> view = self.folder.unrestrictedTraverse('testoid/overridden_view')
>>> view()
'The mouse has been eaten by the eagle'
Test traversal to resources from within ZPT pages:
>>> zcml.load_config('resource.zcml', package=Products.Five.browser.tests)
>>> view = self.folder.unrestrictedTraverse('testoid/parakeet.html')
>>> print view()
<html><body><img alt=""
src="http://nohost/test_folder_1_/testoid/++resource++pattern.png" /></body></html>
Clean up
--------
>>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown()
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:meta="http://namespaces.zope.org/meta"
xmlns:browser="http://namespaces.zope.org/browser"
xmlns:five="http://namespaces.zope.org/five">
<!-- make the zope2.Public permission work -->
<meta:redefinePermission from="zope2.Public" to="zope.Public" />
<!-- attribute page -->
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView"
attribute="eagle"
name="eagle.txt"
permission="zope2.ViewManagementScreens"
/>
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView"
name="eagle.method"
permission="zope2.ViewManagementScreens"
allowed_attributes="eagle"
/>
<!-- attribute page -->
<browser:pages
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView"
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="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView"
template="falcon.pt"
name="falcon.html"
permission="zope2.ViewManagementScreens"
/>
<!-- template page (with simple python expression) -->
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="owl.pt"
name="owl.html"
permission="zope2.ViewManagementScreens"
/>
<!-- template page which calls on context using python and path
expressions -->
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="flamingo.pt"
name="flamingo.html"
permission="zope2.ViewManagementScreens"
/>
<!-- template/class page which calls on context, view, views -->
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView"
template="condor.pt"
name="condor.html"
permission="zope2.ViewManagementScreens"
/>
<!-- template page that defines a macro page -->
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="birdmacro.pt"
name="bird.html"
permission="zope2.ViewManagementScreens"
/>
<!-- template page that uses macro page -->
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="seagull.pt"
name="seagull.html"
permission="zope2.ViewManagementScreens"
/>
<!-- test TALES -->
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="ostrich.pt"
name="ostrich.html"
permission="zope2.ViewManagementScreens"
/>
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="tales_traversal.pt"
name="tales_traversal.html"
permission="zope2.ViewManagementScreens"
/>
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="template_variables.pt"
name="template_variables.html"
permission="zope2.ViewManagementScreens"
/>
<!-- template security -->
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="security.pt"
name="security.html"
permission="zope2.View"
/>
<!-- a publicly accessible page, attribute, template, template/class -->
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView"
attribute="eagle"
name="public_attribute_page"
permission="zope2.Public"
/>
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="owl.pt"
name="public_template_page"
permission="zope2.Public"
/>
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView"
template="falcon.pt"
name="public_template_class_page"
permission="zope2.Public"
/>
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView"
template="parakeet.pt"
name="parakeet.html"
permission="zope2.ViewManagementScreens"
/>
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.CallView"
name="callview.html"
permission="zope2.Public"
/>
<!-- pages from methods/functions/callables that don't have docstrings -->
<browser:pages
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class="Products.Five.browser.tests.pages.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>
<!-- 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
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
module="Products.Five.browser.tests"
directory="pages"
permission="zope2.Public"
/>
<!-- browser:page directives with new style classes are ignored -->
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.NewStyleClass"
name="new_style_class"
attribute="method"
permission="zope2.Public"
/>
<!-- Verify that browser:view works, especially when no specific
view attribute is specified -->
<browser:view
name=""
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView"
permission="zope2.Public"
/>
<!-- XXX this should really be in Five.form.tests -->
<!-- protected edit form for permission check -->
<browser:editform
schema="Products.Five.tests.testing.simplecontent.ISimpleContent"
name="protectededitform.html"
permission="zope2.ViewManagementScreens"
/>
<!-- stuff that we'll override in overrides.zcml -->
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView"
attribute="eagle"
name="overridden_view"
permission="zope2.Public"
/>
</configure>
Functional Browser Pages Test
=============================
This test tests publishing aspects of browser pages. Let's register
some:
>>> import Products.Five.browser.tests
>>> from Products.Five import zcml
>>> zcml.load_config("configure.zcml", Products.Five)
>>> zcml.load_config('pages.zcml', package=Products.Five.browser.tests)
Let's also add one of our stub objects to play with:
>>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent
>>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid')
Docstrings
----------
In Zope 2, objects normally have to have a docstring in order to be
published. This crazy requirement luckily isn't true for Zope 3, so
it should be possible to write docstring-less view classes that are
still published through ZPublisher.
We see that even though the callables have no docstring, they are
published nevertheless:
>>> print http(r"""
... GET /test_folder_1_/testoid/nodoc-function HTTP/1.1
... """)
HTTP/1.1 200 OK
...
No docstring
>>> print http(r"""
... GET /test_folder_1_/testoid/nodoc-method HTTP/1.1
... """)
HTTP/1.1 200 OK
...
No docstring
>>> print http(r"""
... GET /test_folder_1_/testoid/nodoc-object HTTP/1.1
... """)
HTTP/1.1 200 OK
...
No docstring
Security
--------
Browser pages need to be protected with a permission. Let's test
those; we start by adding two users:
>>> uf = self.folder.acl_users
>>> uf._doAddUser('viewer', 'secret', [], [])
>>> uf._doAddUser('manager', 'r00t', ['Manager'], [])
>>> protected_view_names = [
... 'eagle.txt', 'falcon.html', 'owl.html', 'flamingo.html',
... 'condor.html', 'protectededitform.html']
>>>
>>> public_view_names = [
... 'public_attribute_page',
... 'public_template_page',
... 'public_template_class_page',
... 'nodoc-method', 'nodoc-function', 'nodoc-object',
... 'dirpage1', 'dirpage2']
>>>
>>> ViewManagementScreens = 'View management screens'
As a normal user we shouldn't get to see those pages protected with
the 'View management screens' permission. Thus we expect a 401
Unauthorized:
>>> for view_name in protected_view_names:
... response = self.publish('/test_folder_1_/testoid/%s' % view_name,
... basic='viewer:secret')
... status = response.getStatus()
... self.failUnless(status == 401, (status, 401, view_name))
The same should apply for the user if he has all other permissions
except 'View management screens':
>>> permissions = self.folder.possible_permissions()
>>> permissions.remove(ViewManagementScreens)
>>> self.folder._addRole('Viewer')
>>> self.folder.manage_role('Viewer', permissions)
>>> self.folder.manage_addLocalRoles('viewer', ['Viewer'])
>>> for view_name in protected_view_names:
... response = self.publish('/test_folder_1_/testoid/%s' % view_name,
... basic='viewer:secret')
... status = response.getStatus()
... self.failUnless(status == 401, (status, 401, view_name))
If we grant 'View management screens' now, the protected views should
become viewable:
>>> self.folder.manage_role('Viewer', [ViewManagementScreens])
>>> for view_name in protected_view_names:
... response = self.publish('/test_folder_1_/testoid/%s' % view_name,
... basic='viewer:secret')
... status = response.getStatus()
... self.failUnless(status == 200, (status, 200, view_name))
Managers should always be able to view anything, including proctected
stuff:
>>> for view_name in protected_view_names:
... response = self.publish('/test_folder_1_/testoid/%s' % view_name,
... basic='manager:r00t')
... self.assertEqual(response.getStatus(), 200)
All public views should always be accessible by anyone:
>>> for view_name in public_view_names:
... response = self.publish('/test_folder_1_/testoid/%s' % view_name)
... status = response.getStatus()
... self.failUnless(status == 200, (status, 200, view_name))
Miscellaneous
-------------
Zope 2 always wants objects in the traversal graph to have a __name__.
That is also true for views, e.g. a view constructed from a simple
class bearing only a __call__ method:
>>> print http(r'''
... GET /test_folder_1_/testoid/callview.html HTTP/1.1
... ''')
HTTP/1.1 200 OK
...
I was __call__()'ed
Clean up
--------
>>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown()
<html><body><img alt="" src="" tal:attributes="src context/++resource++pattern.png" /></body></html>
<html>
<body>
<!-- fivetest is a Zope 3 style i18n domain, default is a PTS domain -->
<p i18n:domain="fivetest" i18n:translate="">This is a message</p>
<p i18n:domain="PlacelessTranslationService" i18n:translate="">Reload this catalog</p>
</body>
</html>
##############################################################################
#
# Copyright (c) 2005 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.
#
##############################################################################
"""Test the PTS language integration.
$Id: pts_test_languages.py 14400 2005-07-07 17:55:08Z philikon $
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
def test_suite():
from Testing.ZopeTestCase import installProduct, FunctionalDocFileSuite
installProduct('Five')
installProduct('PlacelessTranslationService')
return FunctionalDocFileSuite('pts_test_languages.txt',
package='Products.Five.browser.tests')
if __name__ == '__main__':
framework()
PTS languages
=============
Before we start, we need to set up a manager user to be able to create
the portal:
>>> uf = self.folder.acl_users
>>> uf._doAddUser('manager', 'r00t', ['Manager'], [])
We need to 1) configure the Zope 3 i18n message catalogs, 3) register
the PTS languagees adapter and 3) register our test page:
>>> configure_zcml = """
... <configure
... xmlns="http://namespaces.zope.org/zope"
... xmlns:browser="http://namespaces.zope.org/browser"
... xmlns:i18n="http://namespaces.zope.org/i18n"
... >
... <configure package="Products.Five.tests">
... <i18n:registerTranslations directory="locales" />
... </configure>
...
... <adapter
... for="zope.publisher.interfaces.http.IHTTPRequest"
... provides="zope.i18n.interfaces.IUserPreferredLanguages"
... factory="Products.Five.i18n.PTSLanguages"
... />
...
... <configure package="Products.Five.browser.tests">
... <browser:page
... for="Products.Five.interfaces.IFolder"
... template="pts_test_languages.pt"
... name="pts_test_languages.html"
... permission="zope2.View"
... />
... </configure>
... </configure>
... """
>>> from Products.Five import zcml
>>> zcml.load_string(configure_zcml)
Finally, we need a traversable folder so that the test page we
registered is found:
>>> from Products.Five.tests.testing import manage_addFiveTraversableFolder
>>> manage_addFiveTraversableFolder(self.folder, 'ftf')
Now for some actual testing... Our test page is a simple ZPT
translating two messages from different domains. The first domain is
a Zope 3 style one, the second one comes from PTS.
Both systems should yield the same default language (English) when no
language is specified whatsoever:
>>> print http(r"""
... GET /test_folder_1_/ftf/pts_test_languages.html HTTP/1.1
... """)
HTTP/1.1 200 OK
...
<html>
<body>
<!-- fivetest is a Zope 3 style i18n domain, default is a PTS domain -->
<p>This is a message</p>
<p>Reload this catalog</p>
</body>
</html>
Both systems should honour the HTTP ``Accept-Language`` header in the
same way:
>>> print http(r"""
... GET /test_folder_1_/ftf/pts_test_languages.html HTTP/1.1
... Accept-Language: de
... """)
HTTP/1.1 200 OK
...
<html>
<body>
<!-- fivetest is a Zope 3 style i18n domain, default is a PTS domain -->
<p>Dies ist eine Nachricht</p>
<p>Diesen Katalog neu einlesen</p>
</body>
</html>
Both systems should also honour Localizer-specific ways of determining
the language, for example the ``pts_language`` cookie...
>>> print http(r"""
... GET /test_folder_1_/ftf/pts_test_languages.html HTTP/1.1
... Accept-Language: de
... Cookie: pts_language=en
... """)
HTTP/1.1 200 OK
...
<html>
<body>
<!-- fivetest is a Zope 3 style i18n domain, default is a PTS domain -->
<p>This is a message</p>
<p>Reload this catalog</p>
</body>
</html>
... and the ``language`` form field...
>>> print http(r"""
... GET /test_folder_1_/ftf/pts_test_languages.html?language=en HTTP/1.1
... Accept-Language: de
... """)
HTTP/1.1 200 OK
...
<html>
<body>
<!-- fivetest is a Zope 3 style i18n domain, default is a PTS domain -->
<p>This is a message</p>
<p>Reload this catalog</p>
</body>
</html>
... and both the ``pts_language`` cookie and the ``language`` form field:
>>> print http(r"""
... GET /test_folder_1_/ftf/pts_test_languages.html?language=de HTTP/1.1
... Accept-Language: en
... Cookie: pts_language=fr
... """)
HTTP/1.1 200 OK
...
<html>
<body>
<!-- fivetest is a Zope 3 style i18n domain, default is a PTS domain -->
<p>Dies ist eine Nachricht</p>
<p>Diesen Katalog neu einlesen</p>
</body>
</html>
Testing resources
=================
Set up the test fixtures:
>>> import Products.Five.browser.tests
>>> from Products.Five import zcml
>>> zcml.load_config("configure.zcml", Products.Five)
>>> zcml.load_config('resource.zcml', package=Products.Five.browser.tests)
>>> from Products.Five.tests.testing import manage_addFiveTraversableFolder
>>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid')
>>> import os, glob
>>> _prefix = os.path.dirname(Products.Five.browser.tests.__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))]
Resource types
--------------
>>> from Products.Five.browser.resource import Resource, PageTemplateResource
Template resource
~~~~~~~~~~~~~~~~~
>>> resource = self.folder.unrestrictedTraverse('testoid/++resource++cockatiel.html')
>>> isinstance(resource, Resource)
True
>>> resource()
'http://nohost/test_folder_1_/testoid/++resource++cockatiel.html'
File resource
~~~~~~~~~~~~~
>>> resource = self.folder.unrestrictedTraverse('testoid/++resource++style.css')
>>> isinstance(resource, Resource)
True
>>> resource()
'http://nohost/test_folder_1_/testoid/++resource++style.css'
Image resource
~~~~~~~~~~~~~~
>>> resource = self.folder.unrestrictedTraverse('testoid/++resource++pattern.png')
>>> isinstance(resource, Resource)
True
>>> resource()
'http://nohost/test_folder_1_/testoid/++resource++pattern.png'
Resource directory
~~~~~~~~~~~~~~~~~~
>>> base = 'testoid/++resource++fivetest_resources/%s'
>>> base_url = 'http://nohost/test_folder_1_/' + base
>>> abs_url = self.folder.unrestrictedTraverse(base % '')()
>>> abs_url + '/' == base_url % ''
True
PageTemplateResource's __call__ renders the template
>>> for r in dir_resource_names:
... resource = self.folder.unrestrictedTraverse(base % r)
... self.assert_(isinstance(resource, Resource))
... if not isinstance(resource, PageTemplateResource):
... self.assertEquals(resource(), base_url % r)
Security
--------
>>> from Products.Five.tests.testing.restricted import checkRestricted
>>> from Products.Five.tests.testing.restricted import checkUnauthorized
>>> resource_names = ['cockatiel.html', 'style.css', 'pattern.png']
We should get Unauthorized as long as we're unauthenticated:
>>> for resource in resource_names:
... checkUnauthorized(
... self.folder,
... 'context.restrictedTraverse("testoid/++resource++%s")()' % resource)
>>> base = 'testoid/++resource++fivetest_resources/%s'
>>> for resource in dir_resource_names:
... path = base % resource
... checkUnauthorized(self.folder, 'context.restrictedTraverse("%s")' % path)
Now let's create a manager user account and log in:
>>> uf = self.folder.acl_users
>>> uf._doAddUser('manager', 'r00t', ['Manager'], [])
>>> self.login('manager')
We can now view them all:
>>> for resource in resource_names:
... checkRestricted(
... self.folder,
... 'context.restrictedTraverse("testoid/++resource++%s")()' % resource)
>>> base = 'testoid/++resource++fivetest_resources/%s'
>>> for resource in dir_resource_names:
... path = base % resource
... checkRestricted(self.folder, 'context.restrictedTraverse("%s")' % path)
Clean up
--------
>>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown()
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser">
<!-- 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"
/>
</configure>
\ No newline at end of file
Functional Resource Test
========================
Set up the test fixtures:
>>> import Products.Five.browser.tests
>>> from Products.Five import zcml
>>> zcml.load_config("configure.zcml", Products.Five)
>>> zcml.load_config('resource.zcml', package=Products.Five.browser.tests)
>>> from Products.Five.tests.testing import manage_addFiveTraversableFolder
>>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid')
>>> import os, glob
>>> _prefix = os.path.dirname(Products.Five.browser.tests.__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))]
>>> uf = self.folder.acl_users
>>> uf._doAddUser('manager', 'r00t', ['Manager'], [])
Image resource
~~~~~~~~~~~~~~
>>> print http(r'''
... GET /test_folder_1_/testoid/++resource++pattern.png HTTP/1.1
... Authorization: Basic manager:r00t
... ''')
HTTP/1.1 200 OK
...
File resource
~~~~~~~~~~~~~
>>> print http(r'''
... GET /test_folder_1_/testoid/++resource++style.css HTTP/1.1
... Authorization: Basic manager:r00t
... ''')
HTTP/1.1 200 OK
...
Template resource
~~~~~~~~~~~~~~~~~
>>> print http(r'''
... GET /test_folder_1_/testoid/++resource++cockatiel.html HTTP/1.1
... Authorization: Basic manager:r00t
... ''')
HTTP/1.1 200 OK
...
Resource directory
~~~~~~~~~~~~~~~~~~
Page templates aren't guaranteed to render, so exclude them from the test:
>>> base_url = '/test_folder_1_/testoid/++resource++fivetest_resources/%s'
>>> for r in dir_resource_names:
... if r.endswith('.pt'):
... continue
... response = self.publish(base_url % r, basic='manager:r00t')
... self.assertEquals(200, response.getStatus())
Clean up
--------
>>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown()
<html metal:use-macro="context/@@bird.html/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__" />
Test layer and skin support
===========================
Let's register a test layer and test skin:
>>> import Products.Five.browser.tests
>>> from Products.Five import zcml
>>> zcml.load_config("configure.zcml", Products.Five)
>>> zcml.load_config("skin.zcml", package=Products.Five.browser.tests)
Let's add a test object that we'll access the test page from:
>>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent
>>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid')
The view was registered on a different layer than 'default', that's
why we can't access it straight away:
>>> print http(r"""
... GET /test_folder_1_/testoid/eagle.html HTTP/1.1
... """)
HTTP/1.1 404 Not Found
...
It works when we explicitly use the skin that includes that layer:
>>> print http(r"""
... GET /test_folder_1_/testoid/++skin++TestSkin/eagle.html HTTP/1.1
... """)
HTTP/1.1 200 OK
...
The eagle has landed
Or when we make that skin the default skin:
>>> zcml.load_string('''
... <browser:defaultSkin
... xmlns:browser="http://namespaces.zope.org/browser"
... name="TestSkin" />
... ''')
>>> print http(r"""
... GET /test_folder_1_/testoid/eagle.html HTTP/1.1
... """)
HTTP/1.1 200 OK
...
The eagle has landed
Clean up
--------
>>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown()
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:meta="http://namespaces.zope.org/meta"
xmlns:browser="http://namespaces.zope.org/browser">
<!-- make the zope2.Public permission work -->
<meta:redefinePermission from="zope2.Public" to="zope.Public" />
<browser:layer name="test" />
<browser:skin
name="TestSkin"
layers="test default"
/>
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView"
attribute="eagle"
name="eagle.html"
permission="zope2.Public"
layer="test"
/>
</configure>
<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>
View is a view: <tal:block
content="python:hasattr(view,'context') and hasattr(view, 'request')" />
Context is testoid: <tal:block content="python:context.id == 'testoid'" />
Contaxt.aq_parent is test_folder_1_: <tal:block
content="python:context.aq_parent.id =='test_folder_1_'" />
Container is context: <tal:block content="python:container is context" />
Here is context: <tal:block content="python:here is context"/>
Nothing is None: <tal:block content="python:nothing is None"/>
Default works: <tal:block replace="non_existent_var|default" />True
Root is the application: <tal:block
replace="python:repr(root).find('Application') != -1" />
Template is a template: <tal:block
replace="python:repr(template.aq_base).startswith('<ZopeTwoPageTemplateFile')" />
Traverse_subpath exists and is empty: <tal:block
replace="python:traverse_subpath == []" />
Request is a request: <tal:block
replace="python:getattr(request, 'RESPONSE', None) is not None" />
User is manager: <tal:block replace="python:str(user) == 'manager'" />
Options exist: <tal:block replace="python:options is not None" />
Attrs exist: <tal:block replace="python:attrs is not None" />
Repeat exists: <tal:block replace="python:repeat is not None" />
Loop exists: <tal:block replace="python:loop is not None" />
Modules exists: <tal:block replace="python:modules is not None" />
\ No newline at end of file
##############################################################################
#
# Copyright (c) 2004, 2005 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.
#
##############################################################################
"""Test AbsoluteURL
$Id: test_absoluteurl.py 14595 2005-07-12 21:26:12Z philikon $
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
def test_absoluteurl():
"""This tests the absolute url view (IAbsoluteURL or @@absolute_url),
in particular the breadcrumb functionality.
First we make some preparations:
>>> import Products.Five
>>> from Products.Five import zcml
>>> zcml.load_config("configure.zcml", Products.Five)
>>> from Products.Five.tests.testing import manage_addFiveTraversableFolder
>>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid')
A simple traversal will yield us the @@absolute_url view:
>>> view = self.folder.unrestrictedTraverse('testoid/@@absolute_url')
>>> view()
'http://nohost/test_folder_1_/testoid'
IAbsoluteURL also defines a breadcrumbs() method that returns a
simple Python structure:
>>> for crumb in view.breadcrumbs():
... info = crumb.items()
... info.sort()
... info
[('name', ''), ('url', 'http://nohost')]
[('name', 'test_folder_1_'), ('url', 'http://nohost/test_folder_1_')]
[('name', 'testoid'), ('url', 'http://nohost/test_folder_1_/testoid')]
This test assures and demonstrates that the absolute url stops
traversing through an object's parents when it has reached the
root object. In Zope 3 this is marked with the IContainmentRoot
interface:
>>> from zope.interface import directlyProvides, providedBy
>>> from zope.app.traversing.interfaces import IContainmentRoot
>>> directlyProvides(self.folder, IContainmentRoot)
>>> for crumb in view.breadcrumbs():
... info = crumb.items()
... info.sort()
... info
[('name', 'test_folder_1_'), ('url', 'http://nohost/test_folder_1_')]
[('name', 'testoid'), ('url', 'http://nohost/test_folder_1_/testoid')]
>>> directlyProvides(self.folder,
... providedBy(self.folder) - IContainmentRoot)
The absolute url view is obviously not affected by virtual hosting:
>>> request = self.app.REQUEST
>>> request['PARENTS'] = [self.folder.test_folder_1_]
>>> url = request.setServerURL(
... protocol='http', hostname='foo.bar.com', port='80')
>>> request.setVirtualRoot('')
>>> for crumb in view.breadcrumbs():
... info = crumb.items()
... info.sort()
... info
[('name', 'test_folder_1_'), ('url', 'http://foo.bar.com')]
[('name', 'testoid'), ('url', 'http://foo.bar.com/testoid')]
Clean up:
>>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown()
"""
def test_suite():
from Testing.ZopeTestCase import ZopeDocTestSuite
return ZopeDocTestSuite()
if __name__ == '__main__':
framework()
##############################################################################
#
# Copyright (c) 2004, 2005 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.
#
##############################################################################
"""Test adding views
$Id: test_adding.py 14595 2005-07-12 21:26:12Z philikon $
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
def test_suite():
from Testing.ZopeTestCase import ZopeDocFileSuite
return ZopeDocFileSuite('adding.txt',
package="Products.Five.browser.tests")
if __name__ == '__main__':
framework()
##############################################################################
#
# Copyright (c) 2004, 2005 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.
#
##############################################################################
"""Test Default View functionality
$Id: test_defaultview.py 14595 2005-07-12 21:26:12Z philikon $
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
def test_default_view():
"""
Test default view functionality
Let's register a couple of default views and make our stub classes
default viewable:
>>> import Products.Five.browser.tests
>>> from Products.Five import zcml
>>> zcml.load_config("configure.zcml", Products.Five)
>>> zcml.load_config('defaultview.zcml', Products.Five.browser.tests)
Now let's add a couple of stub objects:
>>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent
>>> from Products.Five.tests.testing.simplecontent import manage_addCallableSimpleContent
>>> from Products.Five.tests.testing.simplecontent import manage_addIndexSimpleContent
>>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid')
>>> manage_addCallableSimpleContent(self.folder, 'testcall', 'TestCall')
>>> manage_addIndexSimpleContent(self.folder, 'testindex', 'TestIndex')
As a last act of preparation, we create a manager login:
>>> uf = self.folder.acl_users
>>> uf._doAddUser('manager', 'r00t', ['Manager'], [])
Test a simple default view:
>>> print http(r'''
... GET /test_folder_1_/testoid HTTP/1.1
... Authorization: Basic manager:r00t
... ''')
HTTP/1.1 200 OK
...
The eagle has landed
This tests whether an existing ``index_html`` method is still
supported and called:
>>> print http(r'''
... GET /test_folder_1_/testindex HTTP/1.1
... ''')
HTTP/1.1 200 OK
...
Default index_html called
Disabled __call__ overriding for now. Causese more trouble than it
fixes. Thus, no test here:
#>>> print http(r'''
#... GET /test_folder_1_/testcall HTTP/1.1
#... ''')
#HTTP/1.1 200 OK
#...
#Default __call__ called
Clean up:
>>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown()
"""
def test_suite():
from Testing.ZopeTestCase import FunctionalDocTestSuite
return FunctionalDocTestSuite()
if __name__ == '__main__':
framework()
##############################################################################
#
# Copyright (c) 2004, 2005 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.
#
##############################################################################
"""Unit tests for the i18n framework
$Id: test_i18n.py 14595 2005-07-12 21:26:12Z philikon $
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
def test_zpt_i18n():
"""
Test i18n functionality in ZPTs
>>> configure_zcml = '''
... <configure
... xmlns="http://namespaces.zope.org/zope"
... xmlns:browser="http://namespaces.zope.org/browser"
... xmlns:i18n="http://namespaces.zope.org/i18n">
... <configure package="Products.Five.tests">
... <i18n:registerTranslations directory="locales" />
... </configure>
... <configure package="Products.Five.browser.tests">
... <browser:page
... for="OFS.interfaces.IFolder"
... template="i18n.pt"
... name="i18n.html"
... permission="zope2.View"
... />
... </configure>
... </configure>'''
>>> import Products.Five
>>> from Products.Five import zcml
>>> zcml.load_config("configure.zcml", Products.Five)
>>> zcml.load_string(configure_zcml)
In order to be able to traverse to the PageTemplate view, we need
a traversable object:
>>> from Products.Five.tests.testing import manage_addFiveTraversableFolder
>>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid')
We tell Zope to translate the messages by passing the
``Accept-Language`` header which is processed by the
``IUserPreferredLangauges`` adapter:
>>> print http(r'''
... GET /test_folder_1_/testoid/@@i18n.html HTTP/1.1
... Accept-Language: de
... ''')
HTTP/1.1 200 OK
...
<html>
<body>
<p>Dies ist eine Nachricht</p>
<p>Dies ist eine explizite Nachricht</p>
<p>Dies sind 4 Nachrichten</p>
<p>Dies sind 4 explizite Nachrichten</p>
<table summary="Dies ist ein Attribut">
</table>
<table summary="Explizite Zusammenfassung"
title="Expliziter Titel">
</table>
</body>
</html>
...
Clean up:
>>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown()
"""
def test_suite():
from Testing.ZopeTestCase import FunctionalDocTestSuite
from zope.testing.doctest import ELLIPSIS
return FunctionalDocTestSuite(optionflags=ELLIPSIS)
if __name__ == '__main__':
framework()
##############################################################################
#
# Copyright (c) 2004, 2005 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.
#
##############################################################################
"""Test browser menus
$Id: test_menu.py 14595 2005-07-12 21:26:12Z philikon $
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
def test_menu():
"""
Test menus
Before we can start we need to set up a few things. For menu
configuration, we have to start a new interaction:
>>> import Products.Five.browser.tests
>>> from Products.Five import zcml
>>> zcml.load_config("meta.zcml", Products.Five)
>>> zcml.load_config("permissions.zcml", Products.Five)
>>> zcml.load_config('menu.zcml', package=Products.Five.browser.tests)
>>> from Products.Five.security import newInteraction
>>> newInteraction()
Now for some actual testing... Let's look up the menu we registered:
>>> from Products.Five.traversable import FakeRequest
>>> from zope.app.publication.browser import setDefaultSkin
>>> from zope.app.publisher.browser.menu import getMenu
>>> request = FakeRequest()
>>> setDefaultSkin(request)
>>> menu = getMenu('testmenu', self.folder, request)
It should have
>>> len(menu)
4
Sort menu items by title so we get a stable testable result:
>>> menu.sort(lambda x, y: cmp(x['title'], y['title']))
>>> from pprint import pprint
>>> pprint(menu[0])
{'action': u'@@cockatiel_menu_public.html',
'description': u'',
'extra': None,
'icon': None,
'selected': u'',
'submenu': None,
'title': u'Page in a menu (public)'}
>>> pprint(menu[1])
{'action': u'seagull.html',
'description': u'This is a test menu item',
'extra': None,
'icon': None,
'selected': u'',
'submenu': None,
'title': u'Test Menu Item'}
>>> pprint(menu[2])
{'action': u'parakeet.html',
'description': u'This is a test menu item',
'extra': None,
'icon': None,
'selected': u'',
'submenu': None,
'title': u'Test Menu Item 2'}
>>> pprint(menu[3])
{'action': u'falcon.html',
'description': u'This is a test menu item',
'extra': None,
'icon': None,
'selected': u'',
'submenu': None,
'title': u'Test Menu Item 3'}
Let's create a manager user account and log in.
>>> uf = self.folder.acl_users
>>> uf._doAddUser('manager', 'r00t', ['Manager'], [])
>>> self.login('manager')
>>> newInteraction()
>>> menu = getMenu('testmenu', self.folder, request)
We should get the protected menu items now:
>>> len(menu)
7
>>> menu.sort(lambda x, y: cmp(x['title'], y['title']))
>>> pprint(menu[0])
{'action': u'@@cockatiel_menu_protected.html',
'description': u'',
'extra': None,
'icon': None,
'selected': u'',
'submenu': None,
'title': u'Page in a menu (protected)'}
>>> pprint(menu[1])
{'action': u'@@cockatiel_menu_public.html',
'description': u'',
'extra': None,
'icon': None,
'selected': u'',
'submenu': None,
'title': u'Page in a menu (public)'}
>>> pprint(menu[2])
{'action': u'seagull.html',
'description': u'This is a protected test menu item',
'extra': None,
'icon': None,
'selected': u'',
'submenu': None,
'title': u'Protected Test Menu Item'}
>>> pprint(menu[3])
{'action': u'falcon.html',
'description': u'This is a protected test menu item',
'extra': None,
'icon': None,
'selected': u'',
'submenu': None,
'title': u'Protected Test Menu Item 2'}
>>> pprint(menu[4])
{'action': u'seagull.html',
'description': u'This is a test menu item',
'extra': None,
'icon': None,
'selected': u'',
'submenu': None,
'title': u'Test Menu Item'}
>>> pprint(menu[5])
{'action': u'parakeet.html',
'description': u'This is a test menu item',
'extra': None,
'icon': None,
'selected': u'',
'submenu': None,
'title': u'Test Menu Item 2'}
>>> pprint(menu[6])
{'action': u'falcon.html',
'description': u'This is a test menu item',
'extra': None,
'icon': None,
'selected': u'',
'submenu': None,
'title': u'Test Menu Item 3'}
Clean up:
>>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown()
"""
def test_suite():
from Testing.ZopeTestCase import ZopeDocTestSuite
return ZopeDocTestSuite()
if __name__ == '__main__':
framework()
##############################################################################
#
# Copyright (c) 2004, 2005 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.
#
##############################################################################
"""Test browser pages
$Id: test_pages.py 18840 2005-10-23 09:47:10Z philikon $
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
def test_ViewAcquisitionWrapping():
"""
>>> import Products.Five.browser.tests
>>> from Products.Five import zcml
>>> zcml.load_config("configure.zcml", Products.Five)
>>> zcml.load_config('pages.zcml', package=Products.Five.browser.tests)
>>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent
>>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid')
>>> uf = self.folder.acl_users
>>> uf._doAddUser('manager', 'r00t', ['Manager'], [])
>>> self.login('manager')
>>> view = self.folder.unrestrictedTraverse('testoid/eagle.txt')
>>> view is not None
True
>>> from Products.Five.browser.tests.pages import SimpleView
>>> isinstance(view, SimpleView)
True
>>> view()
'The eagle has landed'
This sucks, but we know it
>>> from Acquisition import aq_parent, aq_base
>>> aq_parent(view.context) is view
True
This is the right way to get the context parent
>>> view.context.aq_inner.aq_parent is not view
True
>>> view.context.aq_inner.aq_parent is self.folder
True
Clean up:
>>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown()
"""
def test_suite():
import unittest
from Testing.ZopeTestCase import installProduct, ZopeDocTestSuite
from Testing.ZopeTestCase import ZopeDocFileSuite
from Testing.ZopeTestCase import FunctionalDocFileSuite
installProduct('PythonScripts') # for Five.tests.testing.restricted
return unittest.TestSuite((
ZopeDocTestSuite(),
ZopeDocFileSuite('pages.txt', package='Products.Five.browser.tests'),
FunctionalDocFileSuite('pages_ftest.txt',
package='Products.Five.browser.tests')
))
return suite
if __name__ == '__main__':
framework()
##############################################################################
#
# Copyright (c) 2004, 2005 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.
#
##############################################################################
"""Test default view recursion
$Id: test_recurse.py 14595 2005-07-12 21:26:12Z philikon $
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
def test_recursion():
"""
Test recursion
This test makes sure that recursion is avoided for view lookup.
First, we need to set up a stub interface...
>>> from zope.interface import Interface, implements
>>> class IRecurse(Interface):
... pass
...
and a class that is callable and has a view method:
>>> from OFS.Traversable import Traversable
>>> class Recurse(Traversable):
... implements(IRecurse)
... def view(self):
... return self()
... def __call__(self):
... return 'foo'
...
Now we make the class default viewable and register a default view
name for it:
>>> from Products.Five.fiveconfigure import classDefaultViewable
>>> classDefaultViewable(Recurse)
>>> from zope.component import provideAdapter
>>> from zope.publisher.interfaces.browser import IBrowserRequest
>>> from zope.component.interfaces import IDefaultViewName
>>> provideAdapter(u'view', (IRecurse, IBrowserRequest), IDefaultViewName)
Here comes the actual test:
>>> ob = Recurse()
>>> ob.view()
'foo'
>>> ob()
'foo'
Clean up adapter registry and monkey patches to classes:
>>> from zope.testing.cleanup import cleanUp
>>> cleanUp()
"""
def test_suite():
from Testing.ZopeTestCase import ZopeDocTestSuite
return ZopeDocTestSuite()
if __name__ == '__main__':
framework()
##############################################################################
#
# Copyright (c) 2004, 2005 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.
#
##############################################################################
"""Test browser resources
$Id: test_resource.py 14595 2005-07-12 21:26:12Z philikon $
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
def test_suite():
import unittest
from Testing.ZopeTestCase import installProduct, ZopeDocFileSuite
from Testing.ZopeTestCase import FunctionalDocFileSuite
installProduct('PythonScripts') # for Five.tests.testing.restricted
return unittest.TestSuite((
ZopeDocFileSuite('resource.txt',
package='Products.Five.browser.tests'),
FunctionalDocFileSuite('resource_ftest.txt',
package='Products.Five.browser.tests'),
))
if __name__ == '__main__':
framework()
##############################################################################
#
# Copyright (c) 2005 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.
#
##############################################################################
"""Test browser pages
$Id$
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
def test_suite():
from Testing.ZopeTestCase import FunctionalDocFileSuite
return FunctionalDocFileSuite('skin.txt',
package='Products.Five.browser.tests')
if __name__ == '__main__':
framework()
##############################################################################
#
# Copyright (c) 2004, 2005 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.
#
##############################################################################
"""Test Five-traversable classes
$Id: test_traversable.py 17580 2005-09-15 16:05:47Z philikon $
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
class SimpleClass(object):
"""Class with no __bobo_traverse__."""
def test_traversable():
"""
Test the behaviour of Five-traversable classes.
>>> import Products.Five
>>> from Products.Five import zcml
>>> zcml.load_config("configure.zcml", Products.Five)
``SimpleContent`` is a traversable class by default. Its fallback
traverser should raise NotFound when traversal fails. (Note: 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.)
>>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent
>>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid')
>>> print http(r'''
... GET /test_folder_1_/testoid/doesntexist HTTP/1.1
... ''')
HTTP/1.1 404 Not Found
...
Now let's take class which already has a __bobo_traverse__ method.
Five should correctly use that as a fallback.
>>> configure_zcml = '''
... <configure xmlns="http://namespaces.zope.org/zope"
... xmlns:meta="http://namespaces.zope.org/meta"
... xmlns:browser="http://namespaces.zope.org/browser"
... xmlns:five="http://namespaces.zope.org/five">
...
... <!-- make the zope2.Public permission work -->
... <meta:redefinePermission from="zope2.Public" to="zope.Public" />
...
... <five:traversable
... class="Products.Five.tests.testing.fancycontent.FancyContent"
... />
... <five:traversable
... class="Products.Five.browser.tests.test_traversable.SimpleClass"
... />
... <five:traversable
... class="Products.Five.tests.testing.FiveTraversableFolder"
... />
...
... <browser:page
... for="Products.Five.tests.testing.fancycontent.IFancyContent"
... class="Products.Five.browser.tests.pages.FancyView"
... attribute="view"
... name="fancyview"
... permission="zope2.Public"
... />
...
... </configure>'''
>>> zcml.load_string(configure_zcml)
>>> from Products.Five.tests.testing.fancycontent import manage_addFancyContent
>>> info = manage_addFancyContent(self.folder, 'fancy', '')
In the following test we let the original __bobo_traverse__ method
kick in:
>>> print http(r'''
... GET /test_folder_1_/fancy/something-else HTTP/1.1
... ''')
HTTP/1.1 200 OK
...
something-else
Of course we also need to make sure that Zope 3 style view lookup
actually works:
>>> print http(r'''
... GET /test_folder_1_/fancy/fancyview HTTP/1.1
... ''')
HTTP/1.1 200 OK
...
Fancy, fancy
Without five traversable, if there had been an attrubute something-else,
the __bobo_traverse__ method would have still been used instead of the
atribute, let's make sure we preserve that behavior.
>>> self.folder.fancy.an_attribute = 'This is an attribute'
>>> print http(r'''
... GET /test_folder_1_/fancy/an_attribute HTTP/1.1
... ''')
HTTP/1.1 200 OK
...
an_attribute
Clean up:
>>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown()
Verify that after cleanup, there's no cruft left from five:traversable::
>>> from Products.Five.browser.tests.test_traversable import SimpleClass
>>> hasattr(SimpleClass, '__bobo_traverse__')
False
>>> hasattr(SimpleClass, '__fallback_traverse__')
False
>>> from Products.Five.tests.testing.fancycontent import FancyContent
>>> hasattr(FancyContent, '__bobo_traverse__')
True
>>> hasattr(FancyContent.__bobo_traverse__, '__five_method__')
False
>>> hasattr(FancyContent, '__fallback_traverse__')
False
"""
def test_suite():
from Testing.ZopeTestCase import FunctionalDocTestSuite
return FunctionalDocTestSuite()
if __name__ == '__main__':
framework()
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:five="http://namespaces.zope.org/five">
<include file="meta.zcml" />
<include file="permissions.zcml" />
<include file="i18n.zcml" />
<include file="event.zcml"/>
<include file="deprecated.zcml"/>
<include package=".site" />
<include package=".browser" />
<include package=".form" />
<include package=".skin" />
<include package=".utilities" />
<include package="zope.app.event" />
<include package="zope.app.traversing" />
<!-- 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"
/>
<!-- 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>
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:five="http://namespaces.zope.org/five">
<!-- deprecated in core Zope, should be fixed there in Zope 2.9 -->
<five:deprecatedManageAddDelete
class="AccessControl.User.BasicUserFolder"/>
<five:deprecatedManageAddDelete
class="App.Factory.Factory"/>
<five:deprecatedManageAddDelete
class="App.Permission.Permission"/>
<five:deprecatedManageAddDelete
class="HelpSys.HelpTopic.HelpTopicBase"/>
<five:deprecatedManageAddDelete
class="OFS.Cache.CacheManager"/>
<five:deprecatedManageAddDelete
class="Products.OFSP.Draft.Draft"/>
<five:deprecatedManageAddDelete
class="Products.OFSP.Version.Version"/>
<five:deprecatedManageAddDelete
class="Products.PythonScripts.PythonScript.PythonScript"/>
<five:deprecatedManageAddDelete
class="Products.Sessions.BrowserIdManager.BrowserIdManager"/>
<five:deprecatedManageAddDelete
class="Products.Sessions.SessionDataManager.SessionDataManager"/>
<five:deprecatedManageAddDelete
class="Products.SiteAccess.VirtualHostMonster.VirtualHostMonster"/>
<five:deprecatedManageAddDelete
class="Products.SiteAccess.SiteRoot.Traverser"/>
<five:deprecatedManageAddDelete
class="Products.SiteErrorLog.SiteErrorLog.SiteErrorLog"/>
<five:deprecatedManageAddDelete
class="Products.ZCatalog.CatalogAwareness.CatalogAware"/>
<five:deprecatedManageAddDelete
class="Products.ZCatalog.CatalogPathAwareness.CatalogAware"/>
<five:deprecatedManageAddDelete
class="ZClasses.Property.ZCommonSheet"/>
<five:deprecatedManageAddDelete
class="ZClasses.ZClass.ZClass"/>
</configure>
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
##############################################################################
#
# Copyright (c) 2005 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.
#
##############################################################################
# this is a package.
<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>
<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>
This directory contains Five tutorial products.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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