Commit 0d930600 authored by 's avatar

- moved Five trunk

parent 0418215f
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.2007-@wcm-solutions.de)
- Malcolm Cleaton (malcolm@jamkit.com)
- Tarek Ziad (tziade@nuxeo.com)
- Whit Morriss (whit@longnow.org)
- Brian Sutherland (jinty@web.de)
- Rocky Burt (rocky@serverzen.com)
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.
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.
* Content Providers and Viewlets
For more information, see ``doc/features.txt``.
How to use Five
---------------
Please see ``doc/manual.txt``.
====
TODO
====
v1.2
----
* backport r22057 from Five-1.3 branch: fix Localizer unit test problem
* i18n domain of site/managesite.pt?
* i18n and translation of utilities/browser/*pt
* can we stop using zLOG and use the logging package? please?
* events (efge)
- get rid of deprecation warnings
- add CHANGES.txt entry
* five:registerClass (yuppie)
- functional test that checks the ZMI if there really is an entry in
the add menu?
* make rendering of resource urls support sites?
v1.3
----
* revisit the test_localsite/test_{get|query}NextSiteManager tests
* correctly treat ZCatalog.Catalog(Path)Awareness.CatalogAware w.r.t
events (efge)
v1.4
----
- fix up locale support in Five.form/Five.formlib
- namedtemplate in Five.formlib?
- l10n (philikon)
- Figure out where add-view redirects should go.
- Instructions on using add views.
- HTTP/WebDAV: support dispatching of all HTTP/WebDAV methods to HTTP
views. If lookup fails, fall back to methods on the object (Zope 2
style). Security is implied by HTTP views.
- FTP: allow manage_FTPstat, manage_FTPget, manage_FTPlist to dispatch to
filerepresentation adapters. Make sure to handle security correctly.
- Grant security stuff through ZCML (<grant />)?
- top-level <require />, <allow /> directives as a ZCML spelling of
allowModule, allowClass
* marker interfaces
- re-add 'views_details' that didn't make it into 1.2/1.3
- add interface for ordering marker interfaces
- improve the way available marker interfaces are detected. It should be
possible to declare interfaces explicitly as marker interfaces.
##############################################################################
#
# 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$
"""
from Products.Five import zcml
# public API provided by Five
# usage: from Products.Five import <something>
from Products.Five.browser import BrowserView
from Products.Five.skin.standardmacros import StandardMacros
# load the site's ZCML tree (usually site.zcml) upon product
# initialization
def initialize(context):
zcml.load_site()
# some convenience methods/decorators
def fivemethod(func):
func.__five_method__ = True
return func
def isFiveMethod(m):
return hasattr(m, '__five_method__')
##############################################################################
#
# Copyright (c) 2006 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.
#
##############################################################################
"""Things needed for backward compatibility
$Id$
"""
from zope.interface import Interface, implements
from zope.component.interfaces import ComponentLookupError
from zope.app.publisher.browser import getDefaultViewName
import zExceptions
import Products.Five.security
from Products.Five import fivemethod
class IBrowserDefault(Interface):
"""Provide a hook for deciding about the default view for an object"""
def defaultView(self, request):
"""Return the object to be published
(usually self) and a sequence of names to traverse to
find the method to be published.
"""
class BrowserDefault(object):
implements(IBrowserDefault)
def __init__(self, context):
self.context = context
def defaultView(self, request):
context = self.context
try:
name = getDefaultViewName(context, request)
return context, [name,]
except ComponentLookupError:
return context, None
class Traversable:
"""A mixin to make an object traversable"""
__five_traversable__ = True
def __bobo_traverse__(self, REQUEST, name):
"""Hook for Zope 2 traversal
This method is called by Zope 2's ZPublisher upon traversal.
It allows us to trick it into faking the Zope 3 traversal system
by using an ITraverser adapter.
"""
try:
return getattr(self, name)
except AttributeError:
pass
try:
return self[name]
except (KeyError, IndexError, TypeError, AttributeError):
pass
raise AttributeError(name)
##############################################################################
#
# 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$
"""
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.
#
##############################################################################
"""Provide basic browser functionality
$Id$
"""
import Acquisition
import zope.publisher.browser
class BrowserView(Acquisition.Explicit, zope.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$
"""
from Acquisition import aq_inner, aq_parent
from OFS.interfaces import ITraversable
from zope.interface import implements
from zope.component import getMultiAdapter
from zope.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.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 = 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.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.
$Id$
"""
__docformat__ = 'restructuredtext'
from warnings import warn
import zope.component
from zope.interface import implements
from zope.publisher.interfaces import IPublishTraverse
from zope.component.interfaces import IFactory
from zope.event import notify
from zope.exceptions import UserError
from zope.lifecycleevent import ObjectCreatedEvent
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 Acquisition import Implicit
from zExceptions import BadRequest
from OFS.SimpleItem import SimpleItem
from Products.Five import BrowserView
from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile
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(zope.component.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 zope.component.getMultiAdapter((self, request), name=view_name)
if name.startswith('@@'):
view_name = name[2:]
else:
view_name = name
view = zope.component.queryMultiAdapter((self, request), name=view_name)
if view is not None:
return view
factory = zope.component.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 (zope.component.queryMultiAdapter((self, self.request), name=view_name)
is not None):
url = "%s/%s=%s" % (
zope.component.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 = zope.component.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 = zope.component.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, 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">
<!-- BBB 2006/05/01 - to be removed after 12 months -->
<browser:defaultView
for="Products.Five.bbb.IBrowserDefault"
name="index.html"
/>
<interface
interface="zope.publisher.interfaces.browser.IBrowserSkinType"
/>
<interface
interface="zope.app.publisher.interfaces.browser.IMenuItemType"
/>
<!-- BBB 2006/02/18, to be removed after 12 months -->
<browser:layer
name="default"
interface="zope.publisher.interfaces.browser.IDefaultBrowserLayer"
bbb_aware="true"
/>
<browser:page
for="*"
name="absolute_url"
class=".absoluteurl.AbsoluteURL"
permission="zope.Public"
allowed_interface="zope.traversing.browser.interfaces.IAbsoluteURL"
/>
<view
for="*"
factory=".absoluteurl.AbsoluteURL"
type="zope.publisher.interfaces.http.IHTTPRequest"
permission="zope.Public"
provides="zope.traversing.browser.interfaces.IAbsoluteURL"
/>
<browser:page
for="zope.traversing.interfaces.IContainmentRoot"
name="absolute_url"
class=".absoluteurl.SiteAbsoluteURL"
permission="zope.Public"
allowed_interface="zope.traversing.browser.interfaces.IAbsoluteURL"
/>
<view
for="zope.traversing.interfaces.IContainmentRoot"
factory=".absoluteurl.SiteAbsoluteURL"
type="zope.publisher.interfaces.http.IHTTPRequest"
permission="zope.Public"
provides="zope.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) 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.
#
##############################################################################
""" Utility functions for decoding browser input and setting the output
encoding.
$Id$
"""
from zope.publisher.browser import isCGI_NAME
from zope.i18n.interfaces import IUserPreferredCharsets
# taken and adapted from zope.publisher.browser.BrowserRequest
def _decode(text, charsets):
"""Try to decode the text using one of the available charsets.
"""
for charset in charsets:
try:
text = unicode(text, charset)
break
except UnicodeError:
pass
return text
def processInputs(request, charsets=None):
if charsets is None:
envadapter = IUserPreferredCharsets(request)
charsets = envadapter.getPreferredCharsets() or ['utf-8']
for name, value in request.form.items():
if not (isCGI_NAME(name) or name.startswith('HTTP_')):
if isinstance(value, str):
request.form[name] = _decode(value, charsets)
elif isinstance(value, list):
request.form[name] = [ _decode(val, charsets)
for val in value
if isinstance(val, str) ]
elif isinstance(value, tuple):
request.form[name] = tuple([ _decode(val, charsets)
for val in value
if isinstance(val, str) ])
def setPageEncoding(request):
"""Set the encoding of the form page via the Content-Type header.
ZPublisher uses the value of this header to determine how to
encode unicode data for the browser.
"""
envadapter = IUserPreferredCharsets(request)
charsets = envadapter.getPreferredCharsets() or ['utf-8']
request.RESPONSE.setHeader(
'Content-Type', 'text/html; charset=%s' % charsets[0])
##############################################################################
#
# 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$
"""
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">
<!-- browser menus -->
<meta:directive
name="menu"
schema="zope.app.publisher.browser.metadirectives.IMenuDirective"
handler="zope.app.publisher.browser.menumeta.menuDirective"
/>
<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:directive
name="menuItem"
schema="zope.app.publisher.browser.metadirectives.IMenuItemDirective"
handler="zope.app.publisher.browser.menumeta.menuItemDirective"
/>
<meta:directive
name="addMenuItem"
schema="zope.app.publisher.browser.metadirectives.IAddMenuItemDirective"
handler="zope.app.publisher.browser.menumeta.addMenuItem"
/>
<!-- browser views -->
<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:directive
name="defaultView"
schema="zope.app.publisher.browser.metadirectives.IDefaultViewDirective"
handler="zope.app.publisher.browser.metaconfigure.defaultView"
/>
<!-- browser pages -->
<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>
<!-- browser resources -->
<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"
/>
<!-- misc. directives -->
<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: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$
"""
import os, sys
from Globals import package_home
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
from Products.PageTemplates.Expressions import SecureModuleImporter
from Products.PageTemplates.Expressions import createTrustedZopeEngine
from zope.app.pagetemplate.viewpagetemplatefile import ViewMapper
_engine = createTrustedZopeEngine()
def getEngine():
return _engine
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)
super(PageTemplateFile, self).__init__(self.filename, _prefix)
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
def pt_getEngine(self):
return getEngine()
def pt_getContext(self):
try:
root = self.getPhysicalRoot()
except AttributeError:
root = self.context.getPhysicalRoot()
# Even if the context isn't a view (when would that be exaclty?),
# there shouldn't be any dange in applying a view, because it
# won't be used. However assuming that a lack of getPhysicalRoot
# implies a missing view causes problems.
view = self._getContext()
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': SecureModuleImporter,
}
if view is not None:
c['view'] = view
c['views'] = ViewMapper(here, request)
return c
ViewPageTemplateFile = ZopeTwoPageTemplateFile
##############################################################################
#
# Copyright (c) 2006 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.
#
##############################################################################
"""Provider expression.
$Id$
"""
import zope.component
from zope.contentprovider import interfaces as cp_interfaces
from zope.contentprovider.tales import addTALNamespaceData
from zope.interface import implements
from zope.tales.expressions import StringExpr
class Z2ProviderExpression(StringExpr):
"""Create a custom provider expression which overrides __call__ to
acquisition wrap the provider so that security lookups can be done."""
implements(cp_interfaces.ITALESProviderExpression)
def __call__(self, econtext):
name = super(Z2ProviderExpression, self).__call__(econtext)
context = econtext.vars['context']
request = econtext.vars['request']
view = econtext.vars['view']
# Try to look up the provider.
provider = zope.component.queryMultiAdapter(
(context, request, view), cp_interfaces.IContentProvider, name)
# Provide a useful error message, if the provider was not found.
if provider is None:
raise cp_interfaces.ContentProviderLookupError(name)
if getattr(provider, '__of__', None) is not None:
provider = provider.__of__(context)
# Insert the data gotten from the context
addTALNamespaceData(provider, econtext)
# Stage 1: Do the state update.
provider.update()
# Stage 2: Render the HTML content.
return provider.render()
##############################################################################
#
# 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$
"""
import os
import urllib
import Acquisition
from OFS.Traversable import Traversable as OFSTraversable
from zope.app.publisher.browser.resources import empty
from zope.app.publisher.fileresource import File, Image
from zope.app.publisher.pagetemplateresource import PageTemplate
from zope.interface import implements
from zope.component import getMultiAdapter
from zope.component.interfaces import IResource
from zope.datetime import time as timeFromDateTimeString
from zope.traversing.browser.interfaces import IAbsoluteURL
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(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)
isfile = os.path.isfile(filename)
isdir = os.path.isdir(filename)
if not (isfile or isdir):
if default is _marker:
raise KeyError(name)
return default
if isfile:
ext = name.split('.')[-1]
factory = self.resource_factories.get(ext, self.default_factory)
else:
factory = DirectoryResourceFactory
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>
##############################################################################
#
# 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 fixtures
$Id$
"""
from zope.interface import Interface, implements
from Products.Five import BrowserView
class IOne(Interface):
"""This is a Zope 3 interface.
"""
class One(object):
'A class'
implements(IOne)
class ViewOne(BrowserView):
'Yet another class'
def my_method(self, arg1, arg2, kw1=None, kw2='D'):
print "CALLED %s %s %s %s" % (arg1, arg2, kw1, kw2)
<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$
"""
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>
...
... <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">
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
name="eagledefaultview.txt"
class=".pages.SimpleView"
attribute="mouse"
permission="zope2.Public"
/>
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
name="index.html"
class=".pages.SimpleView"
attribute="eagle"
permission="zope2.Public"
/>
<class class="Products.Five.tests.testing.simplecontent.IIndexSimpleContent">
<require permission="zope2.Public"
attributes="index_html"/>
</class>
<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>
<p tal:content="view/this_is_a_message">Text should be inserted here and translated automatically</p>
<p tal:content="view/this_is_a_message" i18n:translate="">Text should be inserted here and translated</p>
</body>
</html>
##############################################################################
#
# Copyright (c) 2006 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 i18n.
$Id$
"""
from zope.i18nmessageid import MessageFactory
_ = MessageFactory('fivetest')
from Products.Five import BrowserView
class I18nView(BrowserView):
this_is_a_message = _(u'This is a message')
<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$
"""
from Products.Five import BrowserView
class SimpleView(BrowserView):
"""More docstring. Please Zope"""
def eagle(self):
"""Docstring"""
return u"The eagle has landed"
def mouse(self):
"""Docstring"""
return u"The mouse has been eaten by the eagle"
class FancyView(BrowserView):
"""Fancy, fancy stuff"""
def view(self):
return u"Fancy, fancy"
class CallView(BrowserView):
def __call__(self):
return u"I was __call__()'ed"
class CallableNoDocstring:
def __call__(self):
return u"No docstring"
def function_no_docstring(self):
return u"No docstring"
class NoDocstringView(BrowserView):
def method(self):
return u"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()
u'The eagle has landed'
A browser page that is a Page Template.
>>> view = self.folder.unrestrictedTraverse('testoid/owl.html')
>>> view()
u'<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()
u'<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()
u'The eagle has landed'
>>> view = self.folder.unrestrictedTraverse('testoid/mouse-page.txt')
>>> isinstance(view, SimpleView)
True
>>> view()
u'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
Make sure new-style classes work fine as view classes:
>>> self.folder.unrestrictedTraverse('testoid/@@new_style_class')
<Products.Five.metaclass.NewStyleClass ...>
At one point browser classes with no attribute and no template
values specified wasn't getting BrowserView mixed in. Lets make
sure it is now:
>>> self.folder.unrestrictedTraverse('testoid/@@new_style_class2')
<Products.Five.metaclass.NewStyleClass ...>
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()
u'<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 zope.component import getMultiAdapter
>>> from zope.publisher.browser import TestRequest
>>> request = TestRequest()
>>> view = 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 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()')
Even when logged in though the private methods should not be accessible:
>>> checkUnauthorized( self.folder,
... 'context.restrictedTraverse("testoid/eagle.method").mouse()')
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()
u'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.View"
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"
/>
<!-- make sure browser:page directives with new style classes work -->
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.NewStyleClass"
name="new_style_class"
attribute="method"
permission="zope2.Public"
/>
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.NewStyleClass"
name="new_style_class2"
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))
Methods of views which were not explicitly declared as allowed should not be
accessible TTW, even if we have the permission to render the view:
>>> response = self.publish('/test_folder_1_/testoid/eagle.method/mouse',
... basic='viewer:secret')
>>> self.assertEqual(response.getStatus(), 401)
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>
=================
Content Providers
=================
We need some tests for the Zope2 versions of the TAL directives for provider.
To this end we have copied the tests from zope.contentprovider and made them
work with Five. We have defined a muber of relevant views which use the
new tal expression in providers.zcml:
>>> from zope.contentprovider import interfaces
>>> import Products.Five.browser.tests
>>> from Products.Five import zcml
>>> zcml.load_config("configure.zcml", Products.Five)
>>> zcml.load_config('provider.zcml', package=Products.Five.browser.tests)
Content Providers
-----------------
Content Provider is a term from the Java world that refers to components that
can provide HTML content. It means nothing more! How the content is found and
returned is totally up to the implementation. The Zope 3 touch to the concept
is that content providers are multi-adapters that are looked up by the
context, request (and thus the layer/skin), and view they are displayed in.
So let's create a simple content provider:
>>> import zope.interface
>>> import zope.component
>>> from zope.publisher.interfaces import browser
>>> class MessageBox(object):
... zope.interface.implements(interfaces.IContentProvider)
... zope.component.adapts(zope.interface.Interface,
... browser.IDefaultBrowserLayer,
... zope.interface.Interface)
... message = u'My Message'
...
... def __init__(self, context, request, view):
... self.__parent__ = view
...
... def update(self):
... pass
...
... def render(self):
... return u'<div class="box">%s</div>' %self.message
The ``update()`` method is executed during phase one. Since no state needs to
be calculated and no data is modified by this simple content provider, it is
an empty implementation. The ``render()`` method implements phase 2 of the
process. We can now instantiate the content provider (manually) and render it:
>>> box = MessageBox(None, None, None)
>>> box.render()
u'<div class="box">My Message</div>'
Since our content provider did not require the context, request or view to
create its HTML content, we were able to pass trivial dummy values into the
constructor. Also note that the provider must have a parent (using the
``__parent__`` attribute) specified at all times. The parent must be the view
the provider appears in.
The TALES ``provider`` Expression
---------------------------------
The ``provider`` expression will look up the name of the content provider,
call it and return the HTML content. The first step, however, will be to
register our content provider with the component architecture:
>>> zope.component.provideAdapter(MessageBox, name='mypage.MessageBox')
The content provider must be registered by name, since the TALES expression
uses the name to look up the provider at run time.
>>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent
>>> manage_addSimpleContent(self.folder, 'content_obj', 'ContentObj')
>>> content = self.folder.content_obj
Finally we publish the view:
>>> print http(r'''
... GET /test_folder_1_/content_obj/main.html HTTP/1.1
... ''')
HTTP/1.1 200 OK
...
<html>
<body>
<h1>My Web Page</h1>
<div class="left-column">
<div class="box">My Message</div>
</div>
<div class="main">
Content here
</div>
</body>
</html>
Failure to lookup a Content Provider
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>>> print http(r'''
... GET /test_folder_1_/content_obj/error.html HTTP/1.1
... ''', handle_errors=False)
Traceback (most recent call last):
...
ContentProviderLookupError: ...mypage.UnknownName...
Additional Data from TAL
~~~~~~~~~~~~~~~~~~~~~~~~
The ``provider`` expression allows also for transferring data from the TAL
context into the content provider. This is accomplished by having the content
provider implement an interface that specifies the attributes and provides
``ITALNamespaceData``:
>>> import zope.schema
>>> class IMessageText(zope.interface.Interface):
... message = zope.schema.Text(title=u'Text of the message box')
>>> zope.interface.directlyProvides(IMessageText,
... interfaces.ITALNamespaceData)
Now the message box can receive its text from the TAL environment:
>>> class DynamicMessageBox(MessageBox):
... zope.interface.implements(IMessageText)
>>> zope.component.provideAdapter(
... DynamicMessageBox, provides=interfaces.IContentProvider,
... name='mypage.DynamicMessageBox')
Now we should get two message boxes with different text:
>>> print http(r'''
... GET /test_folder_1_/content_obj/namespace.html HTTP/1.1
... ''')
HTTP/1.1 200 OK
...
<html>
<body>
<h1>My Web Page</h1>
<div class="left-column">
<div class="box">Hello World!</div>
<div class="box">Hello World again!</div>
</div>
<div class="main">
Content here
</div>
</body>
</html>
Finally, a content provider can also implement several ``ITALNamespaceData``:
>>> class IMessageType(zope.interface.Interface):
... type = zope.schema.TextLine(title=u'The type of the message box')
>>> zope.interface.directlyProvides(IMessageType,
... interfaces.ITALNamespaceData)
We'll change our message box content provider implementation a bit, so the new
information is used:
>>> class BetterDynamicMessageBox(DynamicMessageBox):
... zope.interface.implements(IMessageType)
... type = None
...
... def render(self):
... return u'<div class="box,%s">%s</div>' %(self.type, self.message)
>>> zope.component.provideAdapter(
... BetterDynamicMessageBox, provides=interfaces.IContentProvider,
... name='mypage.MessageBox')
>>> print http(r'''
... GET /test_folder_1_/content_obj/namespace2.html HTTP/1.1
... ''')
HTTP/1.1 200 OK
...
<html>
<body>
<h1>My Web Page</h1>
<div class="left-column">
<div class="box,error">Hello World!</div>
<div class="box,warning">Hello World again!</div>
</div>
<div class="main">
Content here
</div>
</body>
</html>
Now we test a provider using a PageTemplateFile to render itself. It must
inherit from an Acquisition base class so that the template can use Zope 2
security mechanisms:
>>> import os, tempfile
>>> temp_dir = tempfile.mkdtemp()
>>> dynTemplate = os.path.join(temp_dir, 'dynamic_template.pt')
>>> open(dynTemplate, 'w').write(
... 'A simple template: <tal:simple replace="python:view.my_property" />')
>>> from Acquisition import Explicit
>>> from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile
>>> class TemplateProvider(Explicit):
... zope.component.adapts(zope.interface.Interface,
... browser.IDefaultBrowserLayer,
... zope.interface.Interface)
...
... def __init__(self, context, request, view):
... self.__parent__ = view
... self.context = context
... self.request = request
... self.view = view
...
... def update(self):
... pass
... # Is there a better way to tell it to look in the current dir?
... render = ZopeTwoPageTemplateFile(dynTemplate, temp_dir)
... my_property = 'A string for you'
>>> zope.component.provideAdapter(TemplateProvider, name='mypage.TemplateProvider', provides=interfaces.IContentProvider)
>>> print http(r'''
... GET /test_folder_1_/content_obj/template_based.html HTTP/1.1
... ''')
HTTP/1.1 200 OK
...
A simple template: A string for you
Cleanup
-------
>>> import shutil
>>> shutil.rmtree(temp_dir)
<configure xmlns:browser="http://namespaces.zope.org/browser"
xmlns:meta="http://namespaces.zope.org/meta">
<!-- make the zope2.Public permission work -->
<meta:redefinePermission from="zope2.Public" to="zope.Public" />
<!-- stuff for content providers -->
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="provider_messagebox.pt"
name="main.html"
permission="zope2.Public"
/>
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="provider_error.pt"
name="error.html"
permission="zope2.Public"
/>
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="provider_namespace.pt"
name="namespace.html"
permission="zope2.Public"
/>
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="provider_namespace2.pt"
name="namespace2.html"
permission="zope2.Public"
/>
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
template="provider_template_based.pt"
name="template_based.html"
permission="zope2.View"
/>
</configure>
<html>
<body>
<tal:block replace="structure provider:mypage.UnknownName" />
</body>
</html>
<html>
<body>
<h1>My Web Page</h1>
<div class="left-column">
<tal:block replace="structure provider:mypage.MessageBox" />
</div>
<div class="main">
Content here
</div>
</body>
</html>
\ No newline at end of file
<html>
<body>
<h1>My Web Page</h1>
<div class="left-column">
<tal:block define="message string:Hello World!"
replace="structure provider:mypage.DynamicMessageBox" />
<tal:block define="message string:Hello World again!"
replace="structure provider:mypage.DynamicMessageBox" />
</div>
<div class="main">
Content here
</div>
</body>
</html>
<html>
<body>
<h1>My Web Page</h1>
<div class="left-column">
<tal:block define="message string:Hello World!;
type string:error"
replace="structure provider:mypage.MessageBox" />
<tal:block define="message string:Hello World again!;
type string:warning"
replace="structure provider:mypage.MessageBox" />
</div>
<div class="main">
Content here
</div>
</body>
</html>
<tal:block replace="structure provider:mypage.TemplateProvider" />
<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$
"""
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())
We also can traverse into sub-directories:
>>> print http(r'''
... GET /test_folder_1_/testoid/++resource++fivetest_resources/resource_subdir/resource.txt HTTP/1.1
... Authorization: Basic manager:r00t
... ''')
HTTP/1.1 200 OK
...
This is a resource in a subdirectory of a normal resource to test traversal.
<BLANKLINE>
Clean up
--------
>>> from zope.app.testing.placelesssetup import tearDown
>>> tearDown()
This is a resource in a subdirectory of a normal resource to test traversal.
<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__" />
##############################################################################
#
# Copyright (c) 2006 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 skins
$Id$
"""
from zope.publisher.interfaces.browser import IDefaultBrowserLayer
class ITestSkin(IDefaultBrowserLayer):
pass
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" />
<interface
interface=".skin.ITestSkin"
type="zope.publisher.interfaces.browser.IBrowserSkinType"
name="TestSkin"
/>
<browser:page
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView"
attribute="eagle"
name="eagle.html"
permission="zope2.Public"
layer=".skin.ITestSkin"
/>
</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
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.
#
##############################################################################
"""Test adding views
$Id$
"""
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()
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.
from Products.Five import BrowserView
from zope.security.management import checkPermission
class Zope3SecurityView(BrowserView):
def __call__(self, permission):
if checkPermission(permission, self.context):
return "Yes, you have the %r permission." % permission
else:
return "No, you don't have the %r permission." % permission
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.
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