Update Five to version 1.1. The biggest change in Five 1.1 is a

restructuring of the source code package layout and i18n through
Zope 3 message catalogs by default (fallback to older translation
services is possible).

Updates to Five 1.2 and eventually 1.3 will follow as those versions
are released. Zope 2.9 will be released with Five 1.3 (which will have
Zope 3.2 support)
parent 2c82b50d
......@@ -2,6 +2,126 @@
Five Changes
============
Five 1.1 (2005-10-04)
=====================
Features
--------
* When Zope was not in debug mode, an error in a ZCML file would cause Five to
stop loading ZCML completely, making all subsequent products "dead". The
effect would typically be that objects appeared to have no views at all.
Now a ZCML error will only stop the ZCML loading for that product, but the
rest of the products will load as usual. A traceback will still be printed
in the event log.
In debug mode the behaviour has not changed; a ZCML error will stop Zope
startup completely, and print a traceback if running in foreground mode.
Restructuring
-------------
* The deprecated FivePageTemplateFile was removed, and the erroneous use of
this by EditView was changed.
Bugfixes
--------
* Repaired 'forms.txt' test which expected an error page when passing
'handle_errors' as False; it now expects an Unauthorized traceback.
Note that this test fails on Zope 2.8.1, which incorrectly ignored
'handle_errors'.
* FiveTraversable should only do a view lookup and not call the traverse
method of its superclass.
* Fixed manage_beforeDelete triggering for classes using five:sendEvents.
* The redefinePermission directive was falsely registered under the
``zope`` namespace, not the ``meta`` namespace as it is in Zope 3.
* Some parts of add.pt and edit.pt were not translated correctly or not
translated at all. The fix depends on TAL changes in Zope 2.8.1 and changes
in Zope X3-3.0.1 (shipped with 2.8.1). Form i18n is still broken with older
Zope versions.
* 'zope' domain translations are now set up by default. Form i18n needs them.
* Added backwards compatibility for some moved classes (AddForm, EditForm,
ContentAdding)
* The ZPT variable 'container' makes little sense in Zope3/Five, but is now
always set to be the same as 'here' which is normal Zope2 behaviour.
It is in Five 1.0.x set to be the same as 'view' which breaks some templates.
* In some hard to replicate cases, using the "modules" variable in ZPT cause
an AuthenticationError. Using the secure module importer fixes this.
* If you used some parts of Zope 3 (for example the mail delivery) Five 1.1
transaction backport would conflict with Zope 3s transaction module.
This is now solved.
Five 1.1b (2005-07-13)
======================
Features
--------
* Zope 3-style i18n support has been provided. Apart from being able
to register translations through ZCML, Five now lets Zope 2 ZPTs
automatically use Zope 3 translation domains. Fallback to an
old-style translation service (e.g. Localizer or PTS) is supported.
This also includes the detection of preferred languages. See
``doc/i18n.txt`` for more information.
* Added support for Zope 3 -> Zope 2 interface bridging. This
functionality will be part of Zope 2.9, with Five you can already
use it in Zope 2.7/2.8. Since Zope 2 interfaces are rarely used and
their Zope 3 equivalents are more meaningful (for the Component
Architecture), the preferred way of dealing with interface migration
is to write Zope 3 interfaces and bridge them to Zope 2 ones as
needed. To bridge, use the ``Interface.bridge.fromZ3Interface()``
function.
* Support for the standard <factory />, <modulealias /> and <hook />
ZCML directives was added.
* The default browser view name for all objects is now 'index.html',
just as it is in Zope 3. This means that a view by that name will
be looked up if no specific view name is given in the URL.
Restructuring
-------------
* Restructured the Five source code to be easier to navigate in.
Three subpackages were created, Five.browser, Five.form and
Five.skin.
* The former test product, ``FiveTest``, was converted into separate
modules that provide the mock objects for the corresponding tests
and are located right next to them. Common test helpers have been
moved to the Five.testing package. Overall, the testing framework
was much simplified and the individual tests clean up after
themselves much like they do in Zope 3.
* Relocated Zope core interfaces. Future Zope versions will ship with their
own z3 interfaces. Five now patches the older Zope versions to make sure
you can always find the interfaces in 'AccessControl.interfaces',
'Acquisition.interfaces', 'App.interfaces', 'OFS.interfaces' and
'webdav.interfaces'. Please don't use the aliases in 'Five.interfaces' or
'Five.bbb.*interfaces' - they are only provided for backwards
compatibility.
* Zope 2.8 HTTPRequest is no longer patched. It has the required methods.
Bugfixes
--------
* The ZPT variable 'container' did not always contain the parent object
of the context.
* The deprecated get_transaction method is no longer used in Zope 2.8.
Five 1.0.2 (2005-07-12)
=======================
......
......@@ -29,6 +29,10 @@ Five contributors
- Yvo Schubbe (y.2005-@wcm-solutions.de)
- Malcolm Cleaton (malcolm@jamkit.com)
- Tarek Ziad (tziade@nuxeo.com)
Thank you
---------
......
====
TODO
====
- more extensive testing whether event system works with things like Zope 2
folders etc
- ensuring that the event-sending behavior is as close to Zope 3's as
possible. A lot of edge cases with different behavior likely remain,
and things like IObjectModifiedEvents are not sent yet for folders.
- allow the multiple use of five:sendEvents
- allow Zope2 boilerplate context.registerClass be configured through zcml
- Figure out where add-view redirects should go.
- Instructions on using add views.
......@@ -13,7 +13,7 @@
##############################################################################
"""Initialize the Five product
$Id: __init__.py 12915 2005-05-31 10:23:19Z philikon $
$Id: __init__.py 12884 2005-05-30 13:10:41Z philikon $
"""
import Acquisition
from Globals import INSTANCE_HOME
......@@ -23,7 +23,7 @@ import zcml
# public API provided by Five
# usage: from Products.Five import <something>
from browser import BrowserView
from skin import StandardMacros
from skin.standardmacros import StandardMacros
def initialize(context):
zcml.load_site()
##############################################################################
#
# Copyright (c) 2002 Zope Corporation and Contributors.
# Copyright (c) 2004, 2005 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
......@@ -11,251 +11,16 @@
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Adding View
""" Z2 -> Z3 bridge utilities.
The Adding View is used to add new objects to a container. It is sort of a
factory screen.
$Id: adding.py 15515 2005-08-02 17:42:08Z yuppie $
"""
__docformat__ = 'restructuredtext'
from warnings import warn
from zope.interface import implements
from zope.publisher.interfaces import IPublishTraverse
from zope.component.interfaces import IFactory
# BBB: This file will be removed in future versions of Five.
from browser.adding import ContentAdding
from zope.app.exception.interfaces import UserError
from zope.app.container.interfaces import IAdding, INameChooser
from zope.app.container.interfaces import IContainerNamesContainer
from zope.app.container.constraints import checkFactory, checkObject
from zope.app import zapi
from zope.app.event.objectevent import ObjectCreatedEvent
from zope.event import notify
from zExceptions import BadRequest
from Products.Five import BrowserView
from Products.Five.traversable import Traversable
from Products.Five.pagetemplatefile import ZopeTwoPageTemplateFile
from Acquisition import Implicit
from OFS.SimpleItem import SimpleItem
class BasicAdding(Implicit, BrowserView):
implements(IAdding, IPublishTraverse)
def add(self, content):
"""See zope.app.container.interfaces.IAdding
"""
container = self.context
name = self.contentName
chooser = INameChooser(container)
# check precondition
checkObject(container, name, content)
if IContainerNamesContainer.providedBy(container):
# The container picks it's own names.
# We need to ask it to pick one.
name = chooser.chooseName(self.contentName or '', content)
else:
request = self.request
name = request.get('add_input_name', name)
if name is None:
name = chooser.chooseName(self.contentName or '', content)
elif name == '':
name = chooser.chooseName('', content)
else:
# Invoke the name chooser even when we have a
# name. It'll do useful things with it like converting
# the incoming unicode to an ASCII string.
name = chooser.chooseName(name, container)
content.id = name
container._setObject(name, content)
self.contentName = name # Set the added object Name
return container._getOb(name)
contentName = None # usually set by Adding traverser
def nextURL(self):
"""See zope.app.container.interfaces.IAdding"""
# XXX this is definitely not right for all or even most uses
# of Five, but can be overridden by an AddView subclass, using
# the class attribute of a zcml:addform directive
return (str(zapi.getView(self.context, "absolute_url", self.request))
+ '/manage_main')
# set in BrowserView.__init__
request = None
context = None
def renderAddButton(self):
warn("The renderAddButton method is deprecated, use nameAllowed",
DeprecationWarning, 2)
def publishTraverse(self, request, name):
"""See zope.app.container.interfaces.IAdding"""
if '=' in name:
view_name, content_name = name.split("=", 1)
self.contentName = content_name
if view_name.startswith('@@'):
view_name = view_name[2:]
return zapi.getView(self, view_name, request)
if name.startswith('@@'):
view_name = name[2:]
else:
view_name = name
view = zapi.queryView(self, view_name, request)
if view is not None:
return view
factory = zapi.queryUtility(IFactory, name)
if factory is None:
return super(BasicAdding, self).publishTraverse(request, name)
return factory
def action(self, type_name='', id=''):
if not type_name:
raise UserError("You must select the type of object to add.")
if type_name.startswith('@@'):
type_name = type_name[2:]
if '/' in type_name:
view_name = type_name.split('/', 1)[0]
else:
view_name = type_name
if zapi.queryView(self, view_name, self.request) is not None:
url = "%s/%s=%s" % (
zapi.getView(self, "absolute_url", self.request),
type_name, id)
self.request.response.redirect(url)
return
if not self.contentName:
self.contentName = id
factory = zapi.getUtility(IFactory, type_name)
content = factory()
notify(ObjectCreatedEvent(content))
self.add(content)
self.request.response.redirect(self.nextURL())
def namesAccepted(self):
return not IContainerNamesContainer.providedBy(self.context)
def nameAllowed(self):
"""Return whether names can be input by the user."""
return not IContainerNamesContainer.providedBy(self.context)
class Adding(BasicAdding):
menu_id = None
index = ZopeTwoPageTemplateFile("adding.pt")
def addingInfo(self):
"""Return menu data.
This is sorted by title.
"""
container = self.context
menu_service = zapi.getService("BrowserMenu")
result = []
for menu_id in (self.menu_id, 'zope.app.container.add'):
if not menu_id:
continue
for item in menu_service.getMenu(menu_id, self, self.request):
extra = item.get('extra')
if extra:
factory = extra.get('factory')
if factory:
factory = zapi.getUtility(IFactory, factory)
if not checkFactory(container, None, factory):
continue
elif item['extra']['factory'] != item['action']:
item['has_custom_add_view']=True
result.append(item)
result.sort(lambda a, b: cmp(a['title'], b['title']))
return result
def isSingleMenuItem(self):
"Return whether there is single menu item or not."
return len(self.addingInfo()) == 1
def hasCustomAddView(self):
"This should be called only if there is `singleMenuItem` else return 0"
if self.isSingleMenuItem():
menu_item = self.addingInfo()[0]
if 'has_custom_add_view' in menu_item:
return True
return False
class ContentAdding(Adding, Traversable, SimpleItem):
menu_id = "add_content"
class ObjectManagerNameChooser:
"""A name chooser for a Zope object manager.
"""
implements(INameChooser)
def __init__(self, context):
self.context = context
def checkName(self, name, object):
# 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
import warnings
warnings.warn("\nThe Products.Five.adding module has been renamed to "
"Products.Five.browser.adding \n"
"and will be disabled starting in Five 1.2.\n",
DeprecationWarning, stacklevel=2)
......@@ -13,7 +13,7 @@
##############################################################################
""" Z2 -> Z3 bridge utilities.
$Id: bridge.py 12915 2005-05-31 10:23:19Z philikon $
$Id: bridge.py 14504 2005-07-11 15:49:59Z yuppie $
"""
from Interface._InterfaceClass import Interface as Z2_InterfaceClass
from Interface import Interface as Z2_Interface
......@@ -39,9 +39,7 @@ def fromZ2Interface(z2i):
return _bridges[z2i]
name = z2i.getName()
bases = [ fromZ2Interface(x) for x in z2i.getBases() ]
attrs = {}
for k, v in z2i.namesAndDescriptions():
......
......@@ -13,7 +13,7 @@
##############################################################################
"""Utils to be reused
$Id: ReuseUtils.py 12912 2005-05-31 09:58:59Z philikon $
$Id: ReuseUtils.py 12907 2005-05-31 06:26:15Z philikon $
"""
from new import function
......
......@@ -13,7 +13,7 @@
##############################################################################
"""Trusted expression
$Id: TrustedExpression.py 12912 2005-05-31 09:58:59Z philikon $
$Id: TrustedExpression.py 17373 2005-09-08 14:48:27Z regebro $
"""
from sys import modules
......@@ -22,17 +22,12 @@ from Products.PageTemplates.PythonExpr import PythonExpr
from Products.PageTemplates.Expressions import \
SubPathExpr, PathExpr, \
StringExpr, \
getEngine, installHandlers
getEngine, installHandlers,\
SecureModuleImporter
from ReuseUtils import rebindFunction
class _ModuleImporter:
def __getitem__(self, module):
__import__(module)
return modules[module]
ModuleImporter = _ModuleImporter()
ModuleImporter = SecureModuleImporter
def trustedTraverse(ob, path, ignored,):
if not path: return self
......@@ -110,3 +105,4 @@ getEngine = rebindFunction(getEngine,
)
......@@ -11,36 +11,22 @@
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Dummy objects and views for the security tests
"""Provide basic browser functionality
$Id: dummy.py 12915 2005-05-31 10:23:19Z philikon $
$Id: __init__.py 18841 2005-10-23 09:57:38Z philikon $
"""
from zope.interface import Interface, implements
from Products.Five import BrowserView
import Acquisition
from AccessControl import ClassSecurityInfo
from Globals import InitializeClass
class IDummy(Interface):
"""Just a marker interface"""
class DummyView(BrowserView):
"""A dummy view"""
class BrowserView(Acquisition.Explicit):
security = ClassSecurityInfo()
def foo(self):
"""A foo"""
return 'A foo view'
def __init__(self, context, request):
self.context = context
self.request = request
class Dummy1:
implements(IDummy)
def foo(self): pass
def bar(self): pass
def baz(self): pass
def keg(self): pass
def wot(self): pass
# XXX do not create any methods on the subclass called index_html,
# as this makes Zope 2 traverse into that first!
class Dummy2(Dummy1):
security = ClassSecurityInfo()
security.declarePublic('foo')
security.declareProtected('View management screens', 'bar')
security.declarePrivate('baz')
security.declareProtected('View management screens', 'keg')
InitializeClass(BrowserView)
##############################################################################
#
# Copyright (c) 2004, 2005 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Absolute URL
$Id: absoluteurl.py 13254 2005-06-09 21:40:41Z philikon $
"""
from Acquisition import aq_inner, aq_parent
from OFS.interfaces import ITraversable
from zope.interface import implements
from zope.app import zapi
from zope.app.traversing.browser.interfaces import IAbsoluteURL
from Products.Five.browser import BrowserView
class AbsoluteURL(BrowserView):
"""An adapter for Zope3-style absolute_url using Zope2 methods
(original: zope.app.traversing.browser.absoluteurl)
"""
implements(IAbsoluteURL)
def __init__(self, context, request):
self.context, self.request = context, request
def __str__(self):
context = aq_inner(self.context)
return context.absolute_url()
__call__ = __str__
def breadcrumbs(self):
context = aq_inner(self.context)
container = aq_parent(context)
request = self.request
name = context.getId()
if container is None or self._isVirtualHostRoot() \
or not ITraversable.providedBy(container):
return (
{'name': name, 'url': context.absolute_url()},)
view = zapi.getViewProviding(container, IAbsoluteURL, request)
base = tuple(view.breadcrumbs())
base += (
{'name': name, 'url': ("%s/%s" % (base[-1]['url'], name))},)
return base
def _isVirtualHostRoot(self):
virtualrootpath = self.request.get('VirtualRootPhysicalPath', None)
if virtualrootpath is None:
return False
context = aq_inner(self.context)
return context.restrictedTraverse(virtualrootpath) == context
class SiteAbsoluteURL(AbsoluteURL):
"""An adapter for Zope3-style absolute_url using Zope2 methods
This one is just used to stop breadcrumbs from crumbing up
to the Zope root.
(original: zope.app.traversing.browser.absoluteurl)
"""
def breadcrumbs(self):
context = self.context
request = self.request
return ({'name': context.getId(),
'url': context.absolute_url()
},)
<html metal:use-macro="here/five_template/macros/master">
<html metal:use-macro="context/@@standard_macros/page">
<body>
<div metal:fill-slot="main">
<p>+ screen not yet supported by Five</p>
......
##############################################################################
#
# Copyright (c) 2002-2005 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Adding View
The Adding View is used to add new objects to a container. It is sort of a
factory screen.
"""
__docformat__ = 'restructuredtext'
from warnings import warn
from zope.interface import implements
from zope.publisher.interfaces import IPublishTraverse
from zope.component.interfaces import IFactory
from zope.app.exception.interfaces import UserError
from zope.app.container.interfaces import IAdding, INameChooser
from zope.app.container.interfaces import IContainerNamesContainer
from zope.app.container.constraints import checkFactory, checkObject
from zope.app import zapi
from zope.app.event.objectevent import ObjectCreatedEvent
from zope.event import notify
from zExceptions import BadRequest
from Products.Five import BrowserView
from Products.Five.traversable import Traversable
from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile
from Acquisition import Implicit
from OFS.SimpleItem import SimpleItem
class BasicAdding(Implicit, BrowserView):
implements(IAdding, IPublishTraverse)
def add(self, content):
"""See zope.app.container.interfaces.IAdding
"""
container = self.context
name = self.contentName
chooser = INameChooser(container)
# check precondition
checkObject(container, name, content)
if IContainerNamesContainer.providedBy(container):
# The container picks it's own names.
# We need to ask it to pick one.
name = chooser.chooseName(self.contentName or '', content)
else:
request = self.request
name = request.get('add_input_name', name)
if name is None:
name = chooser.chooseName(self.contentName or '', content)
elif name == '':
name = chooser.chooseName('', content)
else:
# Invoke the name chooser even when we have a
# name. It'll do useful things with it like converting
# the incoming unicode to an ASCII string.
name = chooser.chooseName(name, container)
content.id = name
container._setObject(name, content)
self.contentName = name # Set the added object Name
return container._getOb(name)
contentName = None # usually set by Adding traverser
def nextURL(self):
"""See zope.app.container.interfaces.IAdding"""
# XXX this is definitely not right for all or even most uses
# of Five, but can be overridden by an AddView subclass, using
# the class attribute of a zcml:addform directive
return (str(zapi.getView(self.context, "absolute_url", self.request))
+ '/manage_main')
# set in BrowserView.__init__
request = None
context = None
def renderAddButton(self):
warn("The renderAddButton method is deprecated, use nameAllowed",
DeprecationWarning, 2)
def publishTraverse(self, request, name):
"""See zope.app.container.interfaces.IAdding"""
if '=' in name:
view_name, content_name = name.split("=", 1)
self.contentName = content_name
if view_name.startswith('@@'):
view_name = view_name[2:]
return zapi.getView(self, view_name, request)
if name.startswith('@@'):
view_name = name[2:]
else:
view_name = name
view = zapi.queryView(self, view_name, request)
if view is not None:
return view
factory = zapi.queryUtility(IFactory, name)
if factory is None:
return super(BasicAdding, self).publishTraverse(request, name)
return factory
def action(self, type_name='', id=''):
if not type_name:
raise UserError("You must select the type of object to add.")
if type_name.startswith('@@'):
type_name = type_name[2:]
if '/' in type_name:
view_name = type_name.split('/', 1)[0]
else:
view_name = type_name
if zapi.queryView(self, view_name, self.request) is not None:
url = "%s/%s=%s" % (
zapi.getView(self, "absolute_url", self.request),
type_name, id)
self.request.response.redirect(url)
return
if not self.contentName:
self.contentName = id
factory = zapi.getUtility(IFactory, type_name)
content = factory()
notify(ObjectCreatedEvent(content))
self.add(content)
self.request.response.redirect(self.nextURL())
def namesAccepted(self):
return not IContainerNamesContainer.providedBy(self.context)
def nameAllowed(self):
"""Return whether names can be input by the user."""
return not IContainerNamesContainer.providedBy(self.context)
class Adding(BasicAdding):
menu_id = None
index = ZopeTwoPageTemplateFile("adding.pt")
def addingInfo(self):
"""Return menu data.
This is sorted by title.
"""
container = self.context
menu_service = zapi.getService("BrowserMenu")
result = []
for menu_id in (self.menu_id, 'zope.app.container.add'):
if not menu_id:
continue
for item in menu_service.getMenu(menu_id, self, self.request):
extra = item.get('extra')
if extra:
factory = extra.get('factory')
if factory:
factory = zapi.getUtility(IFactory, factory)
if not checkFactory(container, None, factory):
continue
elif item['extra']['factory'] != item['action']:
item['has_custom_add_view']=True
result.append(item)
result.sort(lambda a, b: cmp(a['title'], b['title']))
return result
def isSingleMenuItem(self):
"Return whether there is single menu item or not."
return len(self.addingInfo()) == 1
def hasCustomAddView(self):
"This should be called only if there is `singleMenuItem` else return 0"
if self.isSingleMenuItem():
menu_item = self.addingInfo()[0]
if 'has_custom_add_view' in menu_item:
return True
return False
class ContentAdding(Adding, Traversable, SimpleItem):
menu_id = "add_content"
class ObjectManagerNameChooser:
"""A name chooser for a Zope object manager.
"""
implements(INameChooser)
def __init__(self, context):
self.context = context
def checkName(self, name, object):
# 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
......@@ -12,40 +12,19 @@
component="zope.app.publisher.browser.globalbrowsermenuservice.globalBrowserMenuService"
/>
<browser:defaultView name="index.html" />
<browser:page
for="*"
name="absolute_url"
class=".browser.AbsoluteURL"
class=".absoluteurl.AbsoluteURL"
permission="zope.Public"
allowed_interface="zope.app.traversing.browser.interfaces.IAbsoluteURL"
/>
<browser:page
for="*"
template="five_template.pt"
name="five_template"
permission="zope.Public"
/>
<browser:page
for="*"
name="standard_macros"
permission="zope2.View"
class=".skin.StandardMacros"
allowed_interface="zope.interface.common.mapping.IItemMapping"
/>
<browser:page
for="*"
name="form_macros"
permission="zope2.View"
class=".skin.FormMacros"
allowed_interface="zope.interface.common.mapping.IItemMapping"
/>
<view
for="*"
factory=".browser.AbsoluteURL"
factory=".absoluteurl.AbsoluteURL"
type="zope.publisher.interfaces.http.IHTTPRequest"
permission="zope.Public"
provides="zope.app.traversing.browser.interfaces.IAbsoluteURL"
......@@ -54,21 +33,21 @@
<browser:page
for="zope.app.traversing.interfaces.IContainmentRoot"
name="absolute_url"
class=".browser.SiteAbsoluteURL"
class=".absoluteurl.SiteAbsoluteURL"
permission="zope.Public"
allowed_interface="zope.app.traversing.browser.interfaces.IAbsoluteURL"
/>
<view
for="zope.app.traversing.interfaces.IContainmentRoot"
factory=".browser.SiteAbsoluteURL"
factory=".absoluteurl.SiteAbsoluteURL"
type="zope.publisher.interfaces.http.IHTTPRequest"
permission="zope.Public"
provides="zope.app.traversing.browser.interfaces.IAbsoluteURL"
/>
<browser:view
for=".interfaces.IObjectManager"
for="OFS.interfaces.IObjectManager"
name="+"
class=".adding.ContentAdding"
permission="zope2.ViewManagementScreens"
......@@ -79,4 +58,19 @@
</browser:view>
<adapter
for="OFS.interfaces.IObjectManager"
factory=".adding.ObjectManagerNameChooser"
provides="zope.app.container.interfaces.INameChooser"
/>
<!-- Menu access -->
<browser:page
for="*"
name="view_get_menu"
permission="zope.Public"
class=".menu.MenuAccessView"
allowed_interface="zope.app.publisher.interfaces.browser.IMenuAccessView"
/>
</configure>
##############################################################################
#
# Copyright (c) 2005 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Some menu code
$Id: menu.py 14512 2005-07-11 18:40:51Z philikon $
"""
from zope.interface import implements
from zope.app import zapi
from zope.app.publisher.interfaces.browser import IMenuAccessView
from zope.app.servicenames import BrowserMenu
from Products.Five import BrowserView
class MenuAccessView(BrowserView):
implements(IMenuAccessView)
def __getitem__(self, menu_id):
browser_menu_service = zapi.getService(BrowserMenu)
return browser_menu_service.getMenu(menu_id, self.context, self.request)
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:meta="http://namespaces.zope.org/meta">
<meta:directives namespace="http://namespaces.zope.org/browser">
<meta:directive
name="layer"
schema="zope.app.publisher.browser.metadirectives.ILayerDirective"
handler="zope.app.publisher.browser.metaconfigure.layer"
/>
<meta:directive
name="skin"
schema="zope.app.publisher.browser.metadirectives.ISkinDirective"
handler="zope.app.publisher.browser.metaconfigure.skin"
/>
<meta:directive
name="defaultSkin"
schema="zope.app.publisher.browser.metadirectives.IDefaultSkinDirective"
handler="zope.app.publisher.browser.metaconfigure.defaultSkin"
/>
<meta:directive
name="defaultView"
schema="zope.app.publisher.browser.metadirectives.IDefaultViewDirective"
handler=".metaconfigure.defaultView"
/>
<meta:directive
name="page"
schema="zope.app.publisher.browser.metadirectives.IPageDirective"
handler=".metaconfigure.page"
/>
<meta:complexDirective
name="pages"
schema="zope.app.publisher.browser.metadirectives.IPagesDirective"
handler=".metaconfigure.pages"
>
<meta:subdirective
name="page"
schema="zope.app.publisher.browser.metadirectives.IPagesPageSubdirective"
/>
</meta:complexDirective>
<meta:directive
name="resource"
schema="zope.app.publisher.browser.metadirectives.IResourceDirective"
handler=".metaconfigure.resource"
/>
<meta:directive
name="resourceDirectory"
schema="zope.app.publisher.browser.metadirectives.IResourceDirectoryDirective"
handler=".metaconfigure.resourceDirectory"
/>
<meta:directive
name="menu"
schema="zope.app.publisher.browser.metadirectives.IMenuDirective"
handler="zope.app.publisher.browser.globalbrowsermenuservice.menuDirective"
/>
<meta:directive
name="menuItem"
schema="zope.app.publisher.browser.metadirectives.IMenuItemDirective"
handler="zope.app.publisher.browser.globalbrowsermenuservice.menuItemDirective"
/>
<meta:complexDirective
name="menuItems"
schema="zope.app.publisher.browser.metadirectives.IMenuItemsDirective"
handler="zope.app.publisher.browser.globalbrowsermenuservice.menuItemsDirective"
>
<meta:subdirective
name="menuItem"
schema="zope.app.publisher.browser.metadirectives.IMenuItemSubdirective"
/>
</meta:complexDirective>
<meta:complexDirective
name="view"
schema="zope.app.publisher.browser.metadirectives.IViewDirective"
handler=".metaconfigure.view"
>
<meta:subdirective
name="page"
schema="zope.app.publisher.browser.metadirectives.IViewPageSubdirective"
/>
<meta:subdirective
name="defaultPage"
schema="zope.app.publisher.browser.metadirectives.IViewDefaultPageSubdirective"
/>
</meta:complexDirective>
</meta:directives>
</configure>
......@@ -16,7 +16,7 @@
Directives to emulate the 'http://namespaces.zope.org/browser'
namespace in ZCML known from zope.app.
$Id: browserconfigure.py 12915 2005-05-31 10:23:19Z philikon $
$Id: metaconfigure.py 13257 2005-06-09 21:56:39Z philikon $
"""
import os
......@@ -27,21 +27,21 @@ from zope.component.servicenames import Presentation
from zope.publisher.interfaces.browser import IBrowserRequest
from zope.app.publisher.browser.viewmeta import pages as zope_app_pages
from zope.app.publisher.browser.viewmeta import view as zope_app_view
from zope.app.publisher.browser.viewmeta import providesCallable
from zope.app.publisher.browser.globalbrowsermenuservice import\
menuItemDirective
from zope.app.component.metaconfigure import handler
from zope.app.component.interface import provideInterface
from zope.app.form.browser.metaconfigure import BaseFormDirective
from zope.app.container.interfaces import IAdding
from resource import FileResourceFactory, ImageResourceFactory
from resource import PageTemplateResourceFactory
from resource import DirectoryResourceFactory
from browser import BrowserView, EditView, AddView
from metaclass import makeClass
from security import getSecurityInfo, protectClass, protectName,\
initializeClass
from pagetemplatefile import ZopeTwoPageTemplateFile
from Products.Five.browser import BrowserView
from Products.Five.browser.resource import FileResourceFactory, ImageResourceFactory
from Products.Five.browser.resource import PageTemplateResourceFactory
from Products.Five.browser.resource import DirectoryResourceFactory
from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile
from Products.Five.metaclass import makeClass
from Products.Five.security import getSecurityInfo, protectClass, \
protectName, initializeClass
import ExtensionClass
......@@ -425,178 +425,6 @@ def resourceDirectory(_context, name, directory, layer='default',
args = (new_class,)
)
#
# Form generation from schema
#
def EditViewFactory(name, schema, label, permission, layer,
template, default_template, bases, for_, fields,
fulledit_path=None, fulledit_label=None, menu=u''):
s = getGlobalService(Presentation)
class_ = makeClassForTemplate(template, used_for=schema, bases=bases)
class_.schema = schema
class_.label = label
class_.fieldNames = fields
class_.fulledit_path = fulledit_path
if fulledit_path and (fulledit_label is None):
fulledit_label = "Full edit"
class_.fulledit_label = fulledit_label
class_.generated_form = ZopeTwoPageTemplateFile(default_template)
s.provideView(for_, name, IBrowserRequest, class_, layer)
protectClass(class_, permission)
initializeClass(class_)
class FiveFormDirective(BaseFormDirective):
def _processWidgets(self):
if self._widgets:
customWidgetsObject = makeClass('CustomWidgetsMixin', (ExtensionClass.Base,), self._widgets)
self.bases = self.bases + (customWidgetsObject,)
class EditFormDirective(FiveFormDirective):
view = EditView
default_template = 'edit.pt'
title = 'Edit'
def _handle_menu(self):
if self.menu:
menuItemDirective(
self._context, self.menu, self.for_ or self.schema,
'@@' + self.name, self.title, permission=self.permission)
def __call__(self):
self._processWidgets()
self._handle_menu()
self._context.action(
discriminator=self._discriminator(),
callable=EditViewFactory,
args=self._args(),
kw={'menu': self.menu},
)
def AddViewFactory(name, schema, label, permission, layer,
template, default_template, bases, for_,
fields, content_factory, arguments,
keyword_arguments, set_before_add, set_after_add,
menu=u''):
s = getGlobalService(Presentation)
class_ = makeClassForTemplate(template, used_for=schema, bases=bases)
class_.schema = schema
class_.label = label
class_.fieldNames = fields
class_._factory = content_factory
class_._arguments = arguments
class_._keyword_arguments = keyword_arguments
class_._set_before_add = set_before_add
class_._set_after_add = set_after_add
class_.generated_form = ZopeTwoPageTemplateFile(default_template)
s.provideView(for_, name, IBrowserRequest, class_, layer)
protectClass(class_, permission)
initializeClass(class_)
class AddFormDirective(FiveFormDirective):
view = AddView
default_template = 'add.pt'
for_ = IAdding
# default add form information
description = None
content_factory = None
arguments = None
keyword_arguments = None
set_before_add = None
set_after_add = None
def _handle_menu(self):
if self.menu or self.title:
if (not self.menu) or (not self.title):
raise ValueError("If either menu or title are specified, "
"they must both be specified")
# Add forms are really for IAdding components, so do not use
# for=self.schema.
menuItemDirective(
self._context, self.menu, self.for_, '@@' + self.name,
self.title, permission=self.permission,
description=self.description)
def _handle_arguments(self, leftover=None):
schema = self.schema
fields = self.fields
arguments = self.arguments
keyword_arguments = self.keyword_arguments
set_before_add = self.set_before_add
set_after_add = self.set_after_add
if leftover is None:
leftover = fields
if arguments:
missing = [n for n in arguments if n not in fields]
if missing:
raise ValueError("Some arguments are not included in the form",
missing)
optional = [n for n in arguments if not schema[n].required]
if optional:
raise ValueError("Some arguments are optional, use"
" keyword_arguments for them",
optional)
leftover = [n for n in leftover if n not in arguments]
if keyword_arguments:
missing = [n for n in keyword_arguments if n not in fields]
if missing:
raise ValueError(
"Some keyword_arguments are not included in the form",
missing)
leftover = [n for n in leftover if n not in keyword_arguments]
if set_before_add:
missing = [n for n in set_before_add if n not in fields]
if missing:
raise ValueError(
"Some set_before_add are not included in the form",
missing)
leftover = [n for n in leftover if n not in set_before_add]
if set_after_add:
missing = [n for n in set_after_add if n not in fields]
if missing:
raise ValueError(
"Some set_after_add are not included in the form",
missing)
leftover = [n for n in leftover if n not in set_after_add]
self.set_after_add += leftover
else:
self.set_after_add = leftover
def __call__(self):
self._processWidgets()
self._handle_menu()
self._handle_arguments()
self._context.action(
discriminator=self._discriminator(),
callable=AddViewFactory,
args=self._args()+(self.content_factory, self.arguments,
self.keyword_arguments,
self.set_before_add, self.set_after_add),
kw={'menu': self.menu},
)
#
# mixin classes / class factories
#
......@@ -627,14 +455,14 @@ class ViewMixinForTemplates(BrowserView):
def __call__(self, *args, **kw):
return self.index(self, *args, **kw)
def makeClassForTemplate(src, template=None, used_for=None,
def makeClassForTemplate(filename, globals=None, used_for=None,
bases=(), cdict=None):
# XXX needs to deal with security from the bases?
if cdict is None:
cdict = {}
cdict.update({'index': ZopeTwoPageTemplateFile(src, template)})
cdict.update({'index': ZopeTwoPageTemplateFile(filename, globals)})
bases += (ViewMixinForTemplates,)
class_ = makeClass("SimpleViewClass from %s" % src, bases, cdict)
class_ = makeClass("SimpleViewClass from %s" % filename, bases, cdict)
if used_for is not None:
class_.__used_for__ = used_for
......
......@@ -13,21 +13,20 @@
##############################################################################
"""A 'PageTemplateFile' without security restrictions.
$Id: pagetemplatefile.py 12915 2005-05-31 10:23:19Z philikon $
$Id: pagetemplatefile.py 15193 2005-07-27 13:27:04Z regebro $
"""
import os, sys
# Zope 2
from Globals import package_home
from AccessControl import getSecurityManager
from Shared.DC.Scripts.Bindings import Unauthorized, UnauthorizedBinding
from Products.PageTemplates.PageTemplateFile import PageTemplateFile
# Zope 3
from zope.app.pagetemplate.viewpagetemplatefile import ViewMapper
from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile
# Five
from ReuseUtils import rebindFunction
from TrustedExpression import getEngine, ModuleImporter
from Products.Five.browser.ReuseUtils import rebindFunction
from Products.Five.browser.TrustedExpression import getEngine, ModuleImporter
class ZopeTwoPageTemplateFile(PageTemplateFile):
"""A strange hybrid between Zope 2 and Zope 3 page template.
......@@ -65,24 +64,23 @@ class ZopeTwoPageTemplateFile(PageTemplateFile):
getEngine=getEngine)
def _pt_getContext(self):
view = self._getContext()
try:
root = self.getPhysicalRoot()
here = view.context
view = self._getContext()
except AttributeError:
# self has no attribute getPhysicalRoot.
# This typically happens when the template has
# no proper acquisition context. That means it has no view,
# since that's the normal context for a template in Five. /regebro
# self has no attribute getPhysicalRoot. This typically happens
# when the template has no proper acquisition context.
# That also means it has no view. /regebro
root = self.context.getPhysicalRoot()
here = self.context
view = None
here = self.context.aq_inner
request = getattr(root, 'REQUEST', None)
c = {'template': self,
'here': here,
'context': here,
'container': self._getContainer(),
'container': here,
'nothing': None,
'options': {},
'root': root,
......@@ -97,14 +95,3 @@ class ZopeTwoPageTemplateFile(PageTemplateFile):
pt_getContext = rebindFunction(_pt_getContext,
SecureModuleImporter=ModuleImporter)
# this is not in use right now, but would be how to integrate Zope 3 page
# templates instead of Zope 2 page templates
class FivePageTemplateFile(ViewPageTemplateFile):
def pt_getContext(self, instance, request, **_kw):
# instance is a View component
namespace = super(FivePageTemplateFile, self).pt_getContext(
instance, request, **_kw)
namespace['here'] = namespace['context']
return namespace
......@@ -13,15 +13,15 @@
##############################################################################
"""Provide basic resource functionality
$Id: browser.py 5259 2004-06-23 15:59:52Z philikon $
$Id: resource.py 13268 2005-06-10 14:18:23Z philikon $
"""
import os
import urllib
from Acquisition import Explicit, aq_inner, aq_parent
from Acquisition import Explicit
from ComputedAttribute import ComputedAttribute
from browser import BrowserView
from OFS.Traversable import Traversable as OFSTraversable
from zope.exceptions import NotFoundError
from zope.interface import implements
from zope.component.interfaces import IResource
......@@ -33,6 +33,8 @@ from zope.app.publisher.fileresource import File, Image
from zope.app.publisher.pagetemplateresource import PageTemplate
from zope.app.publisher.browser.resources import empty
from Products.Five.browser import BrowserView
_marker = []
class Resource(Explicit):
......@@ -57,7 +59,7 @@ class PageTemplateResource(BrowserView, Resource):
#implements(IBrowserPublisher)
def __browser_default__(self, request):
return self, ('render', )
return self, ('render',)
def render(self):
"""Rendered content"""
......
......@@ -7,12 +7,11 @@ ObjectManagerNameChooser
First we need to import and setup some prerequisites:
>>> from zope.app.container.interfaces import INameChooser
>>> from Products.Five.tests.products.FiveTest.helpers import \
... manage_addFiveTraversableFolder
>>> from Products.Five.testing import manage_addFiveTraversableFolder
>>> from Products.Five.browser.adding import ObjectManagerNameChooser
>>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid')
>>> chooser = INameChooser(self.folder)
>>> 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.
......
<p tal:content="context/mymethod">Alpha</p>
<p tal:content="view/eagle">Beta</p>
<div tal:replace="structure views/flamingo.html">Gamma</div>
\ No newline at end of file
<div tal:replace="structure context/@@flamingo.html">Gamma</div>
\ No newline at end of file
<html>
<body>
<!-- fivetest is a Zope 3 style i18n domain, default is a Localizer domain -->
<p i18n:domain="fivetest" i18n:translate="">This is a message</p>
<p i18n:domain="default" i18n:translate="">Object actions</p>
</body>
</html>
##############################################################################
#
# Copyright (c) 2005 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Test the Localizer language integration for CPS. This test
requires a full blown CPS installation to run. It is therefore
prefixed with ``cps_`` so it won't be picked up by the test runner.
$Id: cps_test_localizer.py 14400 2005-07-07 17:55:08Z philikon $
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
def test_suite():
from Testing.ZopeTestCase import installProduct, FunctionalDocFileSuite
installProduct('Five')
installProduct('BTreeFolder2')
installProduct('CMFCalendar')
installProduct('CMFCore')
installProduct('CMFDefault')
installProduct('CMFTopic')
installProduct('DCWorkflow')
installProduct('Localizer')
installProduct('MailHost')
installProduct('CPSCore')
installProduct('CPSDefault')
installProduct('CPSDirectory')
installProduct('CPSUserFolder')
installProduct('TranslationService')
installProduct('SiteAccess')
# these products should (and used to be) be optional, but they
# aren't right now.
installProduct('CPSForum')
installProduct('CPSSubscriptions')
installProduct('CPSNewsLetters')
installProduct('CPSSchemas')
installProduct('CPSDocument')
installProduct('PortalTransforms')
installProduct('Epoz')
# optional products, but apparently still needed...
installProduct('CPSRSS')
installProduct('CPSChat')
installProduct('CPSCalendar')
installProduct('CPSCollector')
installProduct('CPSMailBoxer')
return FunctionalDocFileSuite('cps_test_localizer.txt',
package='Products.Five.browser.tests')
if __name__ == '__main__':
framework()
Localizer languages
===================
Before we start, we need to set up a manager user to be able to create
the portal:
>>> uf = self.folder.acl_users
>>> uf._doAddUser('manager', 'r00t', ['Manager'], [])
We need to 1) configure the Zope 3 i18n message catalogs, 2) make the
CPS portal traversable, 3) register the Localizer languagees adapter
and 4) register our test page:
>>> configure_zcml = """
... <configure
... xmlns="http://namespaces.zope.org/zope"
... xmlns:browser="http://namespaces.zope.org/browser"
... xmlns:five="http://namespaces.zope.org/five"
... xmlns:i18n="http://namespaces.zope.org/i18n"
... >
... <configure package="Products.Five.tests">
... <i18n:registerTranslations directory="locales" />
... </configure>
...
... <five:traversable class="Products.CPSDefault.Portal.CPSDefaultSite" />
...
... <adapter
... for="zope.publisher.interfaces.http.IHTTPRequest"
... provides="zope.i18n.interfaces.IUserPreferredLanguages"
... factory="Products.Five.i18n.LocalizerLanguages"
... />
...
... <configure package="Products.Five.browser.tests">
... <browser:page
... for="*"
... template="cps_test_localizer.pt"
... name="cps_test_localizer.html"
... permission="zope2.View"
... />
... </configure>
... </configure>
... """
>>> from Products.Five import zcml
>>> zcml.load_string(configure_zcml)
Create a CPS portal. We print an additional line before creating it
because PortalTransforms might print stuff to stdout and doctest
doesn't allow us to ellide a first line.
>>> print "Ignore lines after me"; print http(r"""
... POST /test_folder_1_/manage_addProduct/CPSDefault/manage_addCPSDefaultSite HTTP/1.1
... Authorization: Basic manager:r00t
... Content-Length: 269
... Content-Type: application/x-www-form-urlencoded
...
... id=cps&title=CPS+Portal&description=&manager_id=manager&manager_sn=CPS+manager&manager_givenName=Manager&manager_email=root%40localhost&manager_password=root&manager_password_confirmation=root&langs_list%3Alist=en&langs_list%3Alist=fr&langs_list%3Alist=de&submit=Create""")
Ignore lines after me
...
HTTP/1.1 200 OK
...
Now for some actual testing... Our test page is a simple ZPT
translating two messages from different domains. The first domain is
a Zope 3 style one, the second one comes from Localizer.
Both systems should yield the same default language (English) when no
language is specified whatsoever:
>>> print http(r"""
... GET /test_folder_1_/cps/cps_test_localizer.html HTTP/1.1
... """)
HTTP/1.1 200 OK
...
<html>
<body>
<!-- fivetest is a Zope 3 style i18n domain, default is a Localizer domain -->
<p>This is a message</p>
<p>Object actions</p>
</body>
</html>
Both systems should honour the HTTP ``Accept-Language`` header in the
same way:
>>> print http(r"""
... GET /test_folder_1_/cps/cps_test_localizer.html HTTP/1.1
... Accept-Language: de
... """)
HTTP/1.1 200 OK
...
<html>
<body>
<!-- fivetest is a Zope 3 style i18n domain, default is a Localizer domain -->
<p>Dies ist eine Nachricht</p>
<p>Objekt Aktionen</p>
</body>
</html>
Both systems should also honour Localizer-specific ways of determining
the language, for example the ``LOCALIZER_LANGUAGE`` cookie:
>>> print http(r"""
... GET /test_folder_1_/cps/cps_test_localizer.html HTTP/1.1
... Accept-Language: de
... Cookie: LOCALIZER_LANGUAGE=en
... """)
HTTP/1.1 200 OK
...
<html>
<body>
<!-- fivetest is a Zope 3 style i18n domain, default is a Localizer domain -->
<p>This is a message</p>
<p>Object actions</p>
</body>
</html>
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser"
xmlns:five="http://namespaces.zope.org/five">
<five:defaultViewable
class="Products.Five.testing.simplecontent.SimpleContent" />
<browser:defaultView
for="Products.Five.testing.simplecontent.ISimpleContent"
name="eagledefaultview.txt"
/>
<browser:page
for="Products.Five.testing.simplecontent.ISimpleContent"
name="eagledefaultview.txt"
class=".pages.SimpleView"
attribute="eagle"
permission="zope2.Public"
/>
<!-- this tests whether five:defaultViewable can be called on a class that
already provides __call__, such as our CallableSimpleContent -->
<five:defaultViewable
class="Products.Five.testing.simplecontent.CallableSimpleContent" />
<!-- this tests whether five:defaultViewable can be called on a class that
already provides index_html, such as our IndexSimpleContent -->
<five:defaultViewable
class="Products.Five.testing.simplecontent.IndexSimpleContent" />
<browser:defaultView
for="Products.Five.testing.simplecontent.IIndexSimpleContent"
name="index_html"
/>
</configure>
<p tal:content="context/mymethod">Replaced</p>
<p tal:content="python:context.mymethod()">Replaced</p>
##############################################################################
#
# ZopeTestCase
#
# COPY THIS FILE TO YOUR 'tests' DIRECTORY.
#
# This version of framework.py will use the SOFTWARE_HOME
# environment variable to locate Zope and the Testing package.
#
# If the tests are run in an INSTANCE_HOME installation of Zope,
# Products.__path__ and sys.path with be adjusted to include the
# instance's Products and lib/python directories respectively.
#
# If you explicitly set INSTANCE_HOME prior to running the tests,
# auto-detection is disabled and the specified path will be used
# instead.
#
# If the 'tests' directory contains a custom_zodb.py file, INSTANCE_HOME
# will be adjusted to use it.
#
# If you set the ZEO_INSTANCE_HOME environment variable a ZEO setup
# is assumed, and you can attach to a running ZEO server (via the
# instance's custom_zodb.py).
#
##############################################################################
#
# The following code should be at the top of every test module:
#
# import os, sys
# if __name__ == '__main__':
# execfile(os.path.join(sys.path[0], 'framework.py'))
#
# ...and the following at the bottom:
#
# if __name__ == '__main__':
# framework()
#
##############################################################################
__version__ = '0.2.3'
# Save start state
#
__SOFTWARE_HOME = os.environ.get('SOFTWARE_HOME', '')
__INSTANCE_HOME = os.environ.get('INSTANCE_HOME', '')
if __SOFTWARE_HOME.endswith(os.sep):
__SOFTWARE_HOME = os.path.dirname(__SOFTWARE_HOME)
if __INSTANCE_HOME.endswith(os.sep):
__INSTANCE_HOME = os.path.dirname(__INSTANCE_HOME)
# Find and import the Testing package
#
if not sys.modules.has_key('Testing'):
p0 = sys.path[0]
if p0 and __name__ == '__main__':
os.chdir(p0)
p0 = ''
s = __SOFTWARE_HOME
p = d = s and s or os.getcwd()
while d:
if os.path.isdir(os.path.join(p, 'Testing')):
zope_home = os.path.dirname(os.path.dirname(p))
sys.path[:1] = [p0, p, zope_home]
break
p, d = s and ('','') or os.path.split(p)
else:
print 'Unable to locate Testing package.',
print 'You might need to set SOFTWARE_HOME.'
sys.exit(1)
import Testing, unittest
execfile(os.path.join(os.path.dirname(Testing.__file__), 'common.py'))
# Include ZopeTestCase support
#
if 1: # Create a new scope
p = os.path.join(os.path.dirname(Testing.__file__), 'ZopeTestCase')
if not os.path.isdir(p):
print 'Unable to locate ZopeTestCase package.',
print 'You might need to install ZopeTestCase.'
sys.exit(1)
ztc_common = 'ztc_common.py'
ztc_common_global = os.path.join(p, ztc_common)
f = 0
if os.path.exists(ztc_common_global):
execfile(ztc_common_global)
f = 1
if os.path.exists(ztc_common):
execfile(ztc_common)
f = 1
if not f:
print 'Unable to locate %s.' % ztc_common
sys.exit(1)
# Debug
#
print 'SOFTWARE_HOME: %s' % os.environ.get('SOFTWARE_HOME', 'Not set')
print 'INSTANCE_HOME: %s' % os.environ.get('INSTANCE_HOME', 'Not set')
sys.stdout.flush()
<html i18n:domain="fivetest">
<body>
<p i18n:translate="">This is a message</p>
<p i18n:translate="explicit-msg">This is an explicit message</p>
<p i18n:translate="">These are <span tal:replace="python:4" i18n:name="number" /> messages</p>
<p i18n:translate="explicit-msgs">These are <span tal:replace="python:4" i18n:name="number" /> explicit messages</p>
<table summary="This is an attribute" i18n:attributes="summary">
</table>
<table summary="Explicit summary" title="Explicit title" i18n:attributes="summary explicit-summary; title explicit-title" >
</table>
</body>
</html>
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:meta="http://namespaces.zope.org/meta"
xmlns:browser="http://namespaces.zope.org/browser"
i18n_domain="fivetest">
<!-- make the zope2.Public permission work -->
<meta:redefinePermission from="zope2.Public" to="zope.Public" />
<!-- browser menu support -->
<browser:menu
id="testmenu"
title="Test menu"
/>
<browser:menuItem
for="OFS.interfaces.IFolder"
menu="testmenu"
title="Test Menu Item"
action="seagull.html"
description="This is a test menu item"
permission="zope2.Public"
/>
<browser:menuItem
for="OFS.interfaces.IFolder"
menu="testmenu"
title="Protected Test Menu Item"
action="seagull.html"
description="This is a protected test menu item"
permission="zope2.ViewManagementScreens"
/>
<browser:menuItems
for="OFS.interfaces.IFolder"
menu="testmenu">
<menuItem
title="Test Menu Item 2"
action="parakeet.html"
description="This is a test menu item"
permission="zope2.Public"
/>
<menuItem
title="Test Menu Item 3"
action="falcon.html"
description="This is a test menu item"
permission="zope2.Public"
/>
<menuItem
title="Protected Test Menu Item 2"
action="falcon.html"
description="This is a protected test menu item"
permission="zope2.ViewManagementScreens"
/>
</browser:menuItems>
<!-- page in a menu -->
<browser:page
for="OFS.interfaces.IFolder"
template="cockatiel.pt"
name="cockatiel_menu_public.html"
permission="zope2.Public"
title="Page in a menu (public)"
menu="testmenu"
/>
<browser:page
for="OFS.interfaces.IFolder"
template="cockatiel.pt"
name="cockatiel_menu_protected.html"
permission="zope2.ViewManagementScreens"
title="Page in a menu (protected)"
menu="testmenu"
/>
</configure>
\ No newline at end of file
<ul>
<li tal:repeat="item python:['Alpha', 'Beta', 'Gamma']" tal:content="item"/>
</ul>
<ul>
<li tal:repeat="item python:['Alpha', 'Beta', 'Gamma']" tal:content="python:repeat['item'].index"/>
</ul>
......@@ -3,18 +3,11 @@
<!-- mouse instead of eagle -->
<browser:page
for=".interfaces.ISimpleContent"
class=".browser.SimpleContentView"
for="Products.Five.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView"
attribute="mouse"
name="overridden_view"
permission="zope2.Public"
/>
<!-- OverrideAdapter instead of OriginalAdapter -->
<adapter
for=".interfaces.IOrigin"
provides=".interfaces.IDestination"
factory=".classes.OverrideAdapter"
/>
</configure>
\ No newline at end of file
......@@ -11,17 +11,13 @@
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Browser views for tests.
"""Test browser pages
$Id: browser.py 12915 2005-05-31 10:23:19Z philikon $
$Id: pages.py 12884 2005-05-30 13:10:41Z philikon $
"""
from Products.Five import BrowserView
from Products.Five import StandardMacros as BaseMacros
from simplecontent import FieldSimpleContent
from zope.app.form import CustomWidgetFactory
from zope.app.form.browser import ObjectWidget
class SimpleContentView(BrowserView):
class SimpleView(BrowserView):
"""More docstring. Please Zope"""
def eagle(self):
......@@ -32,7 +28,7 @@ class SimpleContentView(BrowserView):
"""Docstring"""
return "The mouse has been eaten by the eagle"
class FancyContentView(BrowserView):
class FancyView(BrowserView):
"""Fancy, fancy stuff"""
def view(self):
......@@ -69,14 +65,3 @@ class NewStyleClass(object):
def method(self):
"""Docstring"""
return
class StandardMacros(BaseMacros):
macro_pages = ('bird_macros', 'dog_macros')
aliases = {'flying':'birdmacro',
'walking':'dogmacro'}
class ComplexSchemaView:
"""Needs a docstring"""
fish_widget = CustomWidgetFactory(ObjectWidget, FieldSimpleContent)
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.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 != None
True
>>> from Products.Five.browser.tests.pages import SimpleView
>>> isinstance(view, SimpleView)
True
>>> view()
'The eagle has landed'
A browser page that is a Page Template.
>>> view = self.folder.unrestrictedTraverse('testoid/owl.html')
>>> view()
'<p>2</p>\n'
A browser page that is a PageTemplate plus a view class:
>>> view = self.folder.unrestrictedTraverse('testoid/falcon.html')
>>> isinstance(view, SimpleView)
True
>>> view()
'<p>The falcon has taken flight</p>\n'
Test pages that have been registered through the cumulative
<browser:pages> directive:
>>> view = self.folder.unrestrictedTraverse('testoid/eagle-page.txt')
>>> isinstance(view, SimpleView)
True
>>> view()
'The eagle has landed'
>>> view = self.folder.unrestrictedTraverse('testoid/mouse-page.txt')
>>> isinstance(view, SimpleView)
True
>>> view()
'The mouse has been eaten by the eagle'
Zope 2 objects always need a docstring in order to be published. Five
adds a docstring automatically if a view method doesn't have it, but
it shouldn't modify existing ones:
>>> view = self.folder.unrestrictedTraverse('testoid/eagle.txt')
>>> view.eagle.__doc__ == SimpleView.eagle.__doc__
True
Test whether new-style classes are ignored when registering browser
pages with view classes. When traversing for a non-existing view, we
should get an AttributeError:
>>> self.folder.unrestrictedTraverse('testoid/@@new_style_class')
Traceback (most recent call last):
...
AttributeError: @@new_style_class
ZPT-based browser pages
-----------------------
Test access to ``context`` from ZPTs:
>>> view = self.folder.unrestrictedTraverse('testoid/flamingo.html')
>>> print view()
<p>Hello world</p>
<p>Hello world</p>
Test macro access from ZPT pages:
>>> view = self.folder.unrestrictedTraverse('testoid/seagull.html')
>>> view()
'<html><head><title>bird macro</title></head><body>Color: gray</body></html>\n'
Test whether old-style direct traversal still works with a
five:traversable class:
>>> old_view = self.folder.unrestrictedTraverse('testoid/direct')
>>> old_view()
'Direct traversal worked'
test_zpt_things:
>>> view = self.folder.unrestrictedTraverse('testoid/condor.html')
>>> print view()
<p>Hello world</p>
<p>The eagle has landed</p>
<p>Hello world</p>
<p>Hello world</p>
Make sure that tal:repeat works in ZPT browser pages:
>>> view = self.folder.unrestrictedTraverse('testoid/ostrich.html')
>>> print view()
<ul>
<li>Alpha</li>
<li>Beta</li>
<li>Gamma</li>
</ul>
<ul>
<li>0</li>
<li>1</li>
<li>2</li>
</ul>
Test TALES traversal in ZPT pages:
>>> view = self.folder.unrestrictedTraverse('testoid/tales_traversal.html')
>>> print view()
<p>testoid</p>
<p>test_folder_1_</p>
Make sure that global template variables in ZPT pages are correct:
>>> view = self.folder.unrestrictedTraverse('testoid/template_variables.html')
>>> print view()
View is a view: True
Context is testoid: True
Contaxt.aq_parent is test_folder_1_: True
Container is context: True
Here is context: True
Nothing is None: True
Default works: True
Root is the application: True
Template is a template: True
Traverse_subpath exists and is empty: True
Request is a request: True
User is manager: True
Options exist: True
Attrs exist: True
Repeat exists: True
Loop exists: True
Modules exists: True
Make sure that ZPT's aren't a security-less zone. Let's logout and
try to access some protected stuff. Let's not forgot to login again,
of course:
>>> from AccessControl import allow_module
>>> allow_module('smtpd')
>>> self.logout()
>>> view = self.folder.unrestrictedTraverse('testoid/security.html')
>>> print view()
<div>NoneType</div>
<div>smtpd</div>
>>> self.login('manager')
Test pages registered through the <five:pagesFromDirectory /> directive:
>>> view = self.folder.unrestrictedTraverse('testoid/dirpage1')
>>> print view()
<html>
<p>This is page 1</p>
</html>
>>> view = self.folder.unrestrictedTraverse('testoid/dirpage2')
>>> print view()
<html>
<p>This is page 2</p>
</html>
Low-level security
------------------
This tests security on a low level (functional pages test has
high-level security tests). Let's manually look up a protected view:
>>> from Products.Five.traversable import FakeRequest
>>> from zope.app import zapi
>>> request = FakeRequest()
>>> view = zapi.getView(self.folder.testoid, 'eagle.txt', request)
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 it's 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.testing.restricted import checkRestricted
>>> from Products.Five.testing.restricted import checkUnauthorized
As long as we're not authenticated, we should get Unauthorized for
protected views, but we should be able to view the public ones:
>>> self.logout()
>>> for view_name in protected_view_names:
... checkUnauthorized(
... self.folder,
... 'context.restrictedTraverse("testoid/%s")()' % view_name)
>>> for view_name in public_view_names:
... checkRestricted(
... self.folder,
... 'context.restrictedTraverse("testoid/%s")()' % view_name)
>>> self.login('manager')
Being logged in as a manager again, we find that the protected pages
are not accessible to us:
>>> for view_name in protected_view_names:
... checkRestricted(
... self.folder,
... 'context.restrictedTraverse("testoid/%s")()' % view_name)
>>> checkRestricted(
... self.folder,
... 'context.restrictedTraverse("testoid/eagle.method").eagle()')
Other
-----
Make sure that browser pages can be overridden:
>>> zcml.load_string('''
... <includeOverrides
... package="Products.Five.browser.tests"
... file="overrides.zcml" />
... ''')
>>> view = self.folder.unrestrictedTraverse('testoid/overridden_view')
>>> view()
'The mouse has been eaten by the eagle'
Test traversal to resources from within ZPT pages:
>>> zcml.load_config('resource.zcml', package=Products.Five.browser.tests)
>>> view = self.folder.unrestrictedTraverse('testoid/parakeet.html')
>>> print view()
<html><body><img alt=""
src="http://nohost/test_folder_1_/testoid/++resource++pattern.png" /></body></html>
Clean up
--------
>>> from zope.app.tests.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.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView"
attribute="eagle"
name="eagle.txt"
permission="zope2.ViewManagementScreens"
/>
<browser:page
for="Products.Five.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView"
name="eagle.method"
permission="zope2.ViewManagementScreens"
allowed_attributes="eagle"
/>
<!-- attribute page -->
<browser:pages
for="Products.Five.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.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.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.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.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.testing.simplecontent.ISimpleContent"
template="birdmacro.pt"
name="bird.html"
permission="zope2.ViewManagementScreens"
/>
<!-- template page that uses macro page -->
<browser:page
for="Products.Five.testing.simplecontent.ISimpleContent"
template="seagull.pt"
name="seagull.html"
permission="zope2.ViewManagementScreens"
/>
<!-- test TALES -->
<browser:page
for="Products.Five.testing.simplecontent.ISimpleContent"
template="ostrich.pt"
name="ostrich.html"
permission="zope2.ViewManagementScreens"
/>
<browser:page
for="Products.Five.testing.simplecontent.ISimpleContent"
template="tales_traversal.pt"
name="tales_traversal.html"
permission="zope2.ViewManagementScreens"
/>
<browser:page
for="Products.Five.testing.simplecontent.ISimpleContent"
template="template_variables.pt"
name="template_variables.html"
permission="zope2.ViewManagementScreens"
/>
<!-- template security -->
<browser:page
for="Products.Five.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.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView"
attribute="eagle"
name="public_attribute_page"
permission="zope2.Public"
/>
<browser:page
for="Products.Five.testing.simplecontent.ISimpleContent"
template="owl.pt"
name="public_template_page"
permission="zope2.Public"
/>
<browser:page
for="Products.Five.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView"
template="falcon.pt"
name="public_template_class_page"
permission="zope2.Public"
/>
<browser:page
for="Products.Five.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView"
template="parakeet.pt"
name="parakeet.html"
permission="zope2.ViewManagementScreens"
/>
<!-- pages from methods/functions/callables that don't have docstrings -->
<browser:pages
for="Products.Five.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.testing.simplecontent.ISimpleContent"
module="Products.Five.browser.tests"
directory="pages"
permission="zope2.Public"
/>
<!-- browser:page directives with new style classes are ignored -->
<browser:page
for="Products.Five.testing.simplecontent.ISimpleContent"
class=".pages.NewStyleClass"
name="new_style_class"
attribute="method"
permission="zope2.Public"
/>
<!-- Verify that browser:view works, especially when no specific
view attribute is specified -->
<browser:view
name=""
for="Products.Five.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.testing.simplecontent.ISimpleContent"
name="protectededitform.html"
permission="zope2.ViewManagementScreens"
/>
<!-- stuff that we'll override in overrides.zcml -->
<browser:page
for="Products.Five.testing.simplecontent.ISimpleContent"
class=".pages.SimpleView"
attribute="eagle"
name="overridden_view"
permission="zope2.Public"
/>
</configure>
\ No newline at end of file
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.testing.simplecontent import manage_addSimpleContent
>>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid')
Docstrings
----------
In Zope 2, objects normally have to have a docstring in order to be
published. This crazy requirement luckily isn't true for Zope 3, so
it should be possible to write docstring-less view classes that are
still published through ZPublisher.
We see that even though the callables have no docstring, they are
published nevertheless:
>>> print http(r"""
... GET /test_folder_1_/testoid/nodoc-function HTTP/1.1
... """)
HTTP/1.1 200 OK
...
No docstring
>>> print http(r"""
... GET /test_folder_1_/testoid/nodoc-method HTTP/1.1
... """)
HTTP/1.1 200 OK
...
No docstring
>>> print http(r"""
... GET /test_folder_1_/testoid/nodoc-object HTTP/1.1
... """)
HTTP/1.1 200 OK
...
No docstring
Security
--------
Browser pages need to be protected with a permission. Let's test
those; we start by adding two users:
>>> uf = self.folder.acl_users
>>> uf._doAddUser('viewer', 'secret', [], [])
>>> uf._doAddUser('manager', 'r00t', ['Manager'], [])
>>> protected_view_names = [
... 'eagle.txt', 'falcon.html', 'owl.html', 'flamingo.html',
... 'condor.html', 'protectededitform.html']
>>>
>>> public_view_names = [
... 'public_attribute_page',
... 'public_template_page',
... 'public_template_class_page',
... 'nodoc-method', 'nodoc-function', 'nodoc-object',
... 'dirpage1', 'dirpage2']
>>>
>>> ViewManagementScreens = 'View management screens'
As a normal user we shouldn't get to see those pages protected with
the 'View management screens' permission. Thus we expect a 401
Unauthorized:
>>> for view_name in protected_view_names:
... response = self.publish('/test_folder_1_/testoid/%s' % view_name,
... basic='viewer:secret')
... status = response.getStatus()
... self.failUnless(status == 401, (status, 401, view_name))
The same should apply for the user if he has all other permissions
except 'View management screens':
>>> permissions = self.folder.possible_permissions()
>>> permissions.remove(ViewManagementScreens)
>>> self.folder._addRole('Viewer')
>>> self.folder.manage_role('Viewer', permissions)
>>> self.folder.manage_addLocalRoles('viewer', ['Viewer'])
>>> for view_name in protected_view_names:
... response = self.publish('/test_folder_1_/testoid/%s' % view_name,
... basic='viewer:secret')
... status = response.getStatus()
... self.failUnless(status == 401, (status, 401, view_name))
If we grant 'View management screens' now, the protected views should
become viewable:
>>> self.folder.manage_role('Viewer', [ViewManagementScreens])
>>> for view_name in protected_view_names:
... response = self.publish('/test_folder_1_/testoid/%s' % view_name,
... basic='viewer:secret')
... status = response.getStatus()
... self.failUnless(status == 200, (status, 200, view_name))
Managers should always be able to view anything, including proctected
stuff:
>>> for view_name in protected_view_names:
... response = self.publish('/test_folder_1_/testoid/%s' % view_name,
... basic='manager:r00t')
... self.assertEqual(response.getStatus(), 200)
All public views should always be accessible by anyone:
>>> for view_name in public_view_names:
... response = self.publish('/test_folder_1_/testoid/%s' % view_name)
... status = response.getStatus()
... self.failUnless(status == 200, (status, 200, view_name))
Clean up
--------
>>> from zope.app.tests.placelesssetup import tearDown
>>> tearDown()
<html><body><img alt="" src="" tal:attributes="src context/++resource++pattern.png" /></body></html>
<html>
<body>
<!-- fivetest is a Zope 3 style i18n domain, default is a PTS domain -->
<p i18n:domain="fivetest" i18n:translate="">This is a message</p>
<p i18n:domain="PlacelessTranslationService" i18n:translate="">Reload this catalog</p>
</body>
</html>
##############################################################################
#
# Copyright (c) 2005 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Test the PTS language integration.
$Id: pts_test_languages.py 14400 2005-07-07 17:55:08Z philikon $
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
def test_suite():
from Testing.ZopeTestCase import installProduct, FunctionalDocFileSuite
installProduct('Five')
installProduct('PlacelessTranslationService')
return FunctionalDocFileSuite('pts_test_languages.txt',
package='Products.Five.browser.tests')
if __name__ == '__main__':
framework()
PTS languages
=============
Before we start, we need to set up a manager user to be able to create
the portal:
>>> uf = self.folder.acl_users
>>> uf._doAddUser('manager', 'r00t', ['Manager'], [])
We need to 1) configure the Zope 3 i18n message catalogs, 3) register
the PTS languagees adapter and 3) register our test page:
>>> configure_zcml = """
... <configure
... xmlns="http://namespaces.zope.org/zope"
... xmlns:browser="http://namespaces.zope.org/browser"
... xmlns:i18n="http://namespaces.zope.org/i18n"
... >
... <configure package="Products.Five.tests">
... <i18n:registerTranslations directory="locales" />
... </configure>
...
... <adapter
... for="zope.publisher.interfaces.http.IHTTPRequest"
... provides="zope.i18n.interfaces.IUserPreferredLanguages"
... factory="Products.Five.i18n.PTSLanguages"
... />
...
... <configure package="Products.Five.browser.tests">
... <browser:page
... for="Products.Five.interfaces.IFolder"
... template="pts_test_languages.pt"
... name="pts_test_languages.html"
... permission="zope2.View"
... />
... </configure>
... </configure>
... """
>>> from Products.Five import zcml
>>> zcml.load_string(configure_zcml)
Finally, we need a traversable folder so that the test page we
registered is found:
>>> from Products.Five.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.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.testing.restricted import checkRestricted
>>> from Products.Five.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.tests.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.testing import manage_addFiveTraversableFolder
>>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid')
>>> import os, glob
>>> _prefix = os.path.dirname(Products.Five.browser.tests.__file__)
>>> dir_resource_names = [os.path.basename(r) for r in (
... glob.glob('%s/*.png' % _prefix) +
... glob.glob('%s/*.pt' % _prefix) +
... glob.glob('%s/[a-z]*.py' % _prefix) +
... glob.glob('%s/*.css' % _prefix))]
>>> uf = self.folder.acl_users
>>> uf._doAddUser('manager', 'r00t', ['Manager'], [])
Image resource
~~~~~~~~~~~~~~
>>> print http(r'''
... GET /test_folder_1_/testoid/++resource++pattern.png HTTP/1.1
... Authorization: Basic manager:r00t
... ''')
HTTP/1.1 200 OK
...
File resource
~~~~~~~~~~~~~
>>> print http(r'''
... GET /test_folder_1_/testoid/++resource++style.css HTTP/1.1
... Authorization: Basic manager:r00t
... ''')
HTTP/1.1 200 OK
...
Template resource
~~~~~~~~~~~~~~~~~
>>> print http(r'''
... GET /test_folder_1_/testoid/++resource++cockatiel.html HTTP/1.1
... Authorization: Basic manager:r00t
... ''')
HTTP/1.1 200 OK
...
Resource directory
~~~~~~~~~~~~~~~~~~
Page templates aren't guaranteed to render, so exclude them from the test:
>>> base_url = '/test_folder_1_/testoid/++resource++fivetest_resources/%s'
>>> for r in dir_resource_names:
... if r.endswith('.pt'):
... continue
... response = self.publish(base_url % r, basic='manager:r00t')
... self.assertEquals(200, response.getStatus())
Clean up
--------
>>> from zope.app.tests.placelesssetup import tearDown
>>> tearDown()
<html metal:use-macro="context/@@bird.html/birdmacro"><metal:block fill-slot="color">gray</metal:block></html>
View is a view: <tal:block
content="python:hasattr(view,'context') and hasattr(view, 'request')" />
Context is testoid: <tal:block content="python:context.id == 'testoid'" />
Contaxt.aq_parent is test_folder_1_: <tal:block
content="python:context.aq_parent.id =='test_folder_1_'" />
Container is context: <tal:block content="python:container is context" />
Here is context: <tal:block content="python:here is context"/>
Nothing is None: <tal:block content="python:nothing is None"/>
Default works: <tal:block replace="non_existent_var|default" />True
Root is the application: <tal:block
replace="python:repr(root).find('Application') != -1" />
Template is a template: <tal:block
replace="python:repr(template.aq_base).startswith('<ZopeTwoPageTemplateFile')" />
Traverse_subpath exists and is empty: <tal:block
replace="python:traverse_subpath == []" />
Request is a request: <tal:block
replace="python:getattr(request, 'RESPONSE', None) is not None" />
User is manager: <tal:block replace="python:str(user) == 'manager'" />
Options exist: <tal:block replace="python:options is not None" />
Attrs exist: <tal:block replace="python:attrs is not None" />
Repeat exists: <tal:block replace="python:repeat is not None" />
Loop exists: <tal:block replace="python:loop is not None" />
Modules exists: <tal:block replace="python:modules is not None" />
\ No newline at end of file
##############################################################################
#
# Copyright (c) 2004, 2005 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Test AbsoluteURL
$Id: test_absoluteurl.py 14595 2005-07-12 21:26:12Z philikon $
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
def test_absoluteurl():
"""This tests the absolute url view (IAbsoluteURL or @@absolute_url),
in particular the breadcrumb functionality.
First we make some preparations:
>>> import Products.Five
>>> from Products.Five import zcml
>>> zcml.load_config("configure.zcml", Products.Five)
>>> from Products.Five.testing import manage_addFiveTraversableFolder
>>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid')
A simple traversal will yield us the @@absolute_url view:
>>> view = self.folder.unrestrictedTraverse('testoid/@@absolute_url')
>>> view()
'http://nohost/test_folder_1_/testoid'
IAbsoluteURL also defines a breadcrumbs() method that returns a
simple Python structure:
>>> for crumb in view.breadcrumbs():
... info = crumb.items()
... info.sort()
... info
[('name', ''), ('url', 'http://nohost')]
[('name', 'test_folder_1_'), ('url', 'http://nohost/test_folder_1_')]
[('name', 'testoid'), ('url', 'http://nohost/test_folder_1_/testoid')]
This test assures and demonstrates that the absolute url stops
traversing through an object's parents when it has reached the
root object. In Zope 3 this is marked with the IContainmentRoot
interface:
>>> from zope.interface import directlyProvides, providedBy
>>> from zope.app.traversing.interfaces import IContainmentRoot
>>> directlyProvides(self.folder, IContainmentRoot)
>>> for crumb in view.breadcrumbs():
... info = crumb.items()
... info.sort()
... info
[('name', 'test_folder_1_'), ('url', 'http://nohost/test_folder_1_')]
[('name', 'testoid'), ('url', 'http://nohost/test_folder_1_/testoid')]
>>> directlyProvides(self.folder,
... providedBy(self.folder) - IContainmentRoot)
The absolute url view is obviously not affected by virtual hosting:
>>> request = self.app.REQUEST
>>> request['PARENTS'] = [self.folder.test_folder_1_]
>>> url = request.setServerURL(
... protocol='http', hostname='foo.bar.com', port='80')
>>> request.setVirtualRoot('')
>>> for crumb in view.breadcrumbs():
... info = crumb.items()
... info.sort()
... info
[('name', 'test_folder_1_'), ('url', 'http://foo.bar.com')]
[('name', 'testoid'), ('url', 'http://foo.bar.com/testoid')]
Clean up:
>>> from zope.app.tests.placelesssetup import tearDown
>>> tearDown()
"""
def test_suite():
from Testing.ZopeTestCase import ZopeDocTestSuite
return ZopeDocTestSuite()
if __name__ == '__main__':
framework()
......@@ -13,18 +13,16 @@
##############################################################################
"""Test adding views
$Id: test_adding.py 12915 2005-05-31 10:23:19Z philikon $
$Id: test_adding.py 14595 2005-07-12 21:26:12Z philikon $
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
import Products.Five.tests.fivetest # starts Zope, loads Five, etc.
def test_suite():
from Testing.ZopeTestCase import ZopeDocFileSuite
return ZopeDocFileSuite('adding.txt',
package="Products.Five.tests")
package="Products.Five.browser.tests")
if __name__ == '__main__':
framework()
##############################################################################
#
# Copyright (c) 2004, 2005 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Test Default View functionality
$Id: test_defaultview.py 14595 2005-07-12 21:26:12Z philikon $
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
def test_default_view():
"""
Test default view functionality
Let's register a couple of default views and make our stub classes
default viewable:
>>> import Products.Five.browser.tests
>>> from Products.Five import zcml
>>> zcml.load_config("configure.zcml", Products.Five)
>>> zcml.load_config('defaultview.zcml', Products.Five.browser.tests)
Now let's add a couple of stub objects:
>>> from Products.Five.testing.simplecontent import manage_addSimpleContent
>>> from Products.Five.testing.simplecontent import manage_addCallableSimpleContent
>>> from Products.Five.testing.simplecontent import manage_addIndexSimpleContent
>>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid')
>>> manage_addCallableSimpleContent(self.folder, 'testcall', 'TestCall')
>>> manage_addIndexSimpleContent(self.folder, 'testindex', 'TestIndex')
As a last act of preparation, we create a manager login:
>>> uf = self.folder.acl_users
>>> uf._doAddUser('manager', 'r00t', ['Manager'], [])
Test a simple default view:
>>> print http(r'''
... GET /test_folder_1_/testoid HTTP/1.1
... Authorization: Basic manager:r00t
... ''')
HTTP/1.1 200 OK
...
The eagle has landed
This tests whether an existing ``index_html`` method is still
supported and called:
>>> print http(r'''
... GET /test_folder_1_/testindex HTTP/1.1
... ''')
HTTP/1.1 200 OK
...
Default index_html called
Disabled __call__ overriding for now. Causese more trouble than it
fixes. Thus, no test here:
#>>> print http(r'''
#... GET /test_folder_1_/testcall HTTP/1.1
#... ''')
#HTTP/1.1 200 OK
#...
#Default __call__ called
Clean up:
>>> from zope.app.tests.placelesssetup import tearDown
>>> tearDown()
"""
def test_suite():
from Testing.ZopeTestCase import FunctionalDocTestSuite
return FunctionalDocTestSuite()
if __name__ == '__main__':
framework()
##############################################################################
#
# Copyright (c) 2004, 2005 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Unit tests for the i18n framework
$Id: test_i18n.py 14595 2005-07-12 21:26:12Z philikon $
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
def test_zpt_i18n():
"""
Test i18n functionality in ZPTs
>>> configure_zcml = '''
... <configure
... xmlns="http://namespaces.zope.org/zope"
... xmlns:browser="http://namespaces.zope.org/browser"
... xmlns:i18n="http://namespaces.zope.org/i18n">
... <configure package="Products.Five.tests">
... <i18n:registerTranslations directory="locales" />
... </configure>
... <configure package="Products.Five.browser.tests">
... <browser:page
... for="Products.Five.interfaces.IFolder"
... template="i18n.pt"
... name="i18n.html"
... permission="zope2.View"
... />
... </configure>
... </configure>'''
>>> import Products.Five
>>> from Products.Five import zcml
>>> zcml.load_config("configure.zcml", Products.Five)
>>> zcml.load_string(configure_zcml)
In order to be able to traverse to the PageTemplate view, we need
a traversable object:
>>> from Products.Five.testing import manage_addFiveTraversableFolder
>>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid')
We tell Zope to translate the messages by passing the
``Accept-Language`` header which is processed by the
``IUserPreferredLangauges`` adapter:
>>> print http(r'''
... GET /test_folder_1_/testoid/@@i18n.html HTTP/1.1
... Accept-Language: de
... ''')
HTTP/1.1 200 OK
...
<html>
<body>
<p>Dies ist eine Nachricht</p>
<p>Dies ist eine explizite Nachricht</p>
<p>Dies sind 4 Nachrichten</p>
<p>Dies sind 4 explizite Nachrichten</p>
<table summary="Dies ist ein Attribut">
</table>
<table summary="Explizite Zusammenfassung"
title="Expliziter Titel">
</table>
</body>
</html>
...
Clean up:
>>> from zope.app.tests.placelesssetup import tearDown
>>> tearDown()
"""
def test_suite():
from Testing.ZopeTestCase import FunctionalDocTestSuite
from zope.testing.doctest import ELLIPSIS
return FunctionalDocTestSuite(optionflags=ELLIPSIS)
if __name__ == '__main__':
framework()
##############################################################################
#
# Copyright (c) 2004, 2005 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Test browser menus
$Id: test_menu.py 14595 2005-07-12 21:26:12Z philikon $
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
def test_menu():
"""
Test menus
Before we can start we need to set up a few things. For menu
configuration, we have to start a new interaction:
>>> import Products.Five.browser.tests
>>> from Products.Five import zcml
>>> zcml.load_config("configure.zcml", Products.Five)
>>> zcml.load_config('menu.zcml', package=Products.Five.browser.tests)
>>> from Products.Five.security import newInteraction
>>> newInteraction()
Now for some actual testing... Let's look up the menu we registered:
>>> from Products.Five.traversable import FakeRequest
>>> from zope.app.publisher.browser.globalbrowsermenuservice import \\
... globalBrowserMenuService
>>> request = FakeRequest()
>>> menu = globalBrowserMenuService.getMenu(
... 'testmenu', self.folder, request)
It should have
>>> len(menu)
4
Sort menu items by title so we get a stable testable result:
>>> menu.sort(lambda x, y: cmp(x['title'], y['title']))
>>> from pprint import pprint
>>> pprint(menu)
[{'action': '@@cockatiel_menu_public.html',
'description': '',
'extra': None,
'selected': '',
'title': u'Page in a menu (public)'},
{'action': u'seagull.html',
'description': u'This is a test menu item',
'extra': None,
'selected': '',
'title': u'Test Menu Item'},
{'action': u'parakeet.html',
'description': u'This is a test menu item',
'extra': None,
'selected': '',
'title': u'Test Menu Item 2'},
{'action': u'falcon.html',
'description': u'This is a test menu item',
'extra': None,
'selected': '',
'title': u'Test Menu Item 3'}]
Let's create a manager user account and log in.
>>> uf = self.folder.acl_users
>>> uf._doAddUser('manager', 'r00t', ['Manager'], [])
>>> self.login('manager')
>>> newInteraction()
>>> menu = globalBrowserMenuService.getMenu(
... 'testmenu', self.folder, request)
We should get the protected menu items now:
>>> len(menu)
7
>>> menu.sort(lambda x, y: cmp(x['title'], y['title']))
>>> pprint(menu)
[{'action': '@@cockatiel_menu_protected.html',
'description': '',
'extra': None,
'selected': '',
'title': u'Page in a menu (protected)'},
{'action': '@@cockatiel_menu_public.html',
'description': '',
'extra': None,
'selected': '',
'title': u'Page in a menu (public)'},
{'action': u'seagull.html',
'description': u'This is a protected test menu item',
'extra': None,
'selected': '',
'title': u'Protected Test Menu Item'},
{'action': u'falcon.html',
'description': u'This is a protected test menu item',
'extra': None,
'selected': '',
'title': u'Protected Test Menu Item 2'},
{'action': u'seagull.html',
'description': u'This is a test menu item',
'extra': None,
'selected': '',
'title': u'Test Menu Item'},
{'action': u'parakeet.html',
'description': u'This is a test menu item',
'extra': None,
'selected': '',
'title': u'Test Menu Item 2'},
{'action': u'falcon.html',
'description': u'This is a test menu item',
'extra': None,
'selected': '',
'title': u'Test Menu Item 3'}]
Clean up:
>>> from zope.app.tests.placelesssetup import tearDown
>>> tearDown()
"""
def test_suite():
from Testing.ZopeTestCase import ZopeDocTestSuite
return ZopeDocTestSuite()
if __name__ == '__main__':
framework()
##############################################################################
#
# Copyright (c) 2004, 2005 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Test browser pages
$Id: test_pages.py 18840 2005-10-23 09:47:10Z philikon $
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
def test_ViewAcquisitionWrapping():
"""
>>> import Products.Five.browser.tests
>>> from Products.Five import zcml
>>> zcml.load_config("configure.zcml", Products.Five)
>>> zcml.load_config('pages.zcml', package=Products.Five.browser.tests)
>>> from Products.Five.testing.simplecontent import manage_addSimpleContent
>>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid')
>>> uf = self.folder.acl_users
>>> uf._doAddUser('manager', 'r00t', ['Manager'], [])
>>> self.login('manager')
>>> view = self.folder.unrestrictedTraverse('testoid/eagle.txt')
>>> view is not None
True
>>> from Products.Five.browser.tests.pages import SimpleView
>>> isinstance(view, SimpleView)
True
>>> view()
'The eagle has landed'
This sucks, but we know it
>>> from Acquisition import aq_parent, aq_base
>>> aq_parent(view.context) is view
True
This is the right way to get the context parent
>>> view.context.aq_inner.aq_parent is not view
True
>>> view.context.aq_inner.aq_parent is self.folder
True
Clean up:
>>> from zope.app.tests.placelesssetup import tearDown
>>> tearDown()
"""
def test_suite():
import unittest
from Testing.ZopeTestCase import installProduct, ZopeDocTestSuite
from Testing.ZopeTestCase import ZopeDocFileSuite
from Testing.ZopeTestCase import FunctionalDocFileSuite
installProduct('PythonScripts') # for Five.testing.restricted
return unittest.TestSuite((
ZopeDocTestSuite(),
ZopeDocFileSuite('pages.txt',
package='Products.Five.browser.tests'),
FunctionalDocFileSuite('pages_ftest.txt',
package='Products.Five.browser.tests')
))
return suite
if __name__ == '__main__':
framework()
##############################################################################
#
# Copyright (c) 2004, 2005 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Test default view recursion
$Id: test_recurse.py 14595 2005-07-12 21:26:12Z philikon $
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
def test_recursion():
"""
Test recursion
>>> from zope.app.tests.placelesssetup import setUp, tearDown
>>> setUp()
This test makes sure that recursion is avoided for view lookup.
First, we need to set up a stub interface...
>>> from zope.interface import Interface, implements
>>> class IRecurse(Interface):
... pass
...
and a class that is callable and has a view method:
>>> from OFS.Traversable import Traversable
>>> class Recurse(Traversable):
... implements(IRecurse)
... def view(self):
... return self()
... def __call__(self):
... return 'foo'
...
Now we make the class default viewable and register a default view
name for it:
>>> from Products.Five.fiveconfigure import classDefaultViewable
>>> classDefaultViewable(Recurse)
>>> from zope.app import zapi
>>> from zope.publisher.interfaces.browser import IBrowserRequest
>>> pres = zapi.getGlobalService('Presentation')
>>> pres.setDefaultViewName(IRecurse, IBrowserRequest, 'view')
Here comes the actual test:
>>> ob = Recurse()
>>> ob.view()
'foo'
>>> ob()
'foo'
Clean up:
>>> tearDown()
"""
def test_suite():
from Testing.ZopeTestCase import ZopeDocTestSuite
return ZopeDocTestSuite()
if __name__ == '__main__':
framework()
##############################################################################
#
# Copyright (c) 2004, 2005 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Test browser resources
$Id: test_resource.py 14595 2005-07-12 21:26:12Z philikon $
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
def test_suite():
import unittest
from Testing.ZopeTestCase import installProduct, ZopeDocFileSuite
from Testing.ZopeTestCase import FunctionalDocFileSuite
installProduct('PythonScripts') # for Five.testing.restricted
return unittest.TestSuite((
ZopeDocFileSuite('resource.txt',
package='Products.Five.browser.tests'),
FunctionalDocFileSuite('resource_ftest.txt',
package='Products.Five.browser.tests'),
))
if __name__ == '__main__':
framework()
##############################################################################
#
# Copyright (c) 2004, 2005 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Test Five-traversable classes
$Id: test_traversable.py 17580 2005-09-15 16:05:47Z philikon $
"""
import os, sys
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
def test_traversable():
"""
Test the behaviour of Five-traversable classes.
>>> import Products.Five
>>> from Products.Five import zcml
>>> zcml.load_config("configure.zcml", Products.Five)
``SimpleContent`` is a traversable class by default. Its fallback
traverser should raise NotFound when traversal fails. (Note: If
we return None in __fallback_traverse__, this test passes but for
the wrong reason: None doesn't have a docstring so BaseRequest
raises NotFoundError.)
>>> from Products.Five.testing.simplecontent import manage_addSimpleContent
>>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid')
>>> print http(r'''
... GET /test_folder_1_/testoid/doesntexist HTTP/1.1
... ''')
HTTP/1.1 404 Not Found
...
Now let's take class which already has a __bobo_traverse__ method.
Five should correctly use that as a fallback.
>>> configure_zcml = '''
... <configure xmlns="http://namespaces.zope.org/zope"
... xmlns:meta="http://namespaces.zope.org/meta"
... xmlns:browser="http://namespaces.zope.org/browser"
... xmlns:five="http://namespaces.zope.org/five">
...
... <!-- make the zope2.Public permission work -->
... <meta:redefinePermission from="zope2.Public" to="zope.Public" />
...
... <five:traversable
... class="Products.Five.testing.fancycontent.FancyContent"
... />
...
... <browser:page
... for="Products.Five.testing.fancycontent.IFancyContent"
... class="Products.Five.browser.tests.pages.FancyView"
... attribute="view"
... name="fancy"
... permission="zope2.Public"
... />
...
... </configure>'''
>>> zcml.load_string(configure_zcml)
>>> from Products.Five.testing.fancycontent import manage_addFancyContent
>>> info = manage_addFancyContent(self.folder, 'fancy', '')
In the following test we let the original __bobo_traverse__ method
kick in:
>>> print http(r'''
... GET /test_folder_1_/fancy/something-else HTTP/1.1
... ''')
HTTP/1.1 200 OK
...
something-else
Of course we also need to make sure that Zope 3 style view lookup
actually works:
>>> print http(r'''
... GET /test_folder_1_/fancy/fancy HTTP/1.1
... ''')
HTTP/1.1 200 OK
...
Fancy, fancy
Clean up:
>>> from zope.app.tests.placelesssetup import tearDown
>>> tearDown()
"""
def test_suite():
from Testing.ZopeTestCase import FunctionalDocTestSuite
return FunctionalDocTestSuite()
if __name__ == '__main__':
framework()
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser"
xmlns:five="http://namespaces.zope.org/five">
<include file="meta.zcml" />
<include file="services.zcml" />
<include file="interfaces.zcml" />
<include file="permissions.zcml" />
<include file="browser.zcml" />
<include file="i18n.zcml" />
<include package=".browser" />
<include package=".form" />
<include package=".skin" />
<include package="zope.app.event" />
<include package="zope.app.traversing" />
<include package="zope.app.form.browser" />
<!-- do 'traditional' traversing by default; needed by ZPT -->
<adapter
......@@ -31,12 +32,6 @@
provides=".interfaces.IBrowserDefault"
/>
<adapter
for=".interfaces.IObjectManager"
factory=".adding.ObjectManagerNameChooser"
provides="zope.app.container.interfaces.INameChooser"
/>
<!-- this is really lying, but it's to please checkContainer -->
<five:implements class="OFS.ObjectManager.ObjectManager"
interface="zope.app.container.interfaces.IContainer" />
......
......@@ -15,7 +15,7 @@
Use 'structured monkey patching' to enable zope.app.container event sending for
Zope 2 objects.
$Id: eventconfigure.py 12915 2005-05-31 10:23:19Z philikon $
$Id: eventconfigure.py 17810 2005-09-24 09:12:59Z efge $
"""
from Products.Five.fiveconfigure import isFiveMethod
from zope.event import notify
......@@ -25,6 +25,9 @@ from zope.app.container.interfaces import IObjectAddedEvent,\
from zope.app.container.contained import ObjectMovedEvent
from zope.app.event.objectevent import ObjectCopiedEvent
# holds classes that were monkeyed with; for clean up
_monkied = []
# ObjectAddedEvent and ObjectRemovedEvent are different in Zope 2
class ObjectAddedEvent(ObjectMovedEvent):
implements(IObjectAddedEvent)
......@@ -76,15 +79,14 @@ manage_afterAdd.__five_method__ = True
def manage_beforeDelete(self, item, container):
notify(ObjectRemovedEvent(self))
# call original
method = getattr(self, '__five_manage_beforeDelete', None)
method = getattr(self, '__five_original_manage_beforeDelete', None)
if method is not None:
self._five_original_manage_beforeDelete(item, container)
self.__five_original_manage_beforeDelete(item, container)
manage_beforeDelete.__five_method__ = True
def classSendEvents(class_):
"""Make instances of the class send Object*Event.
"""
"""Make instances of the class send Object*Event."""
# tuck away original methods if necessary
for name in ['manage_afterAdd', 'manage_beforeDelete']:
method = getattr(class_, name, None)
......@@ -94,6 +96,8 @@ def classSendEvents(class_):
class_.manage_afterAdd = manage_afterAdd
class_.manage_beforeDelete = manage_beforeDelete
# remember class for clean up
_monkied.append(class_)
def sendEvents(_context, class_):
_context.action(
......@@ -101,3 +105,19 @@ def sendEvents(_context, class_):
callable = classSendEvents,
args=(class_,)
)
# clean up code
from Products.Five.fiveconfigure import killMonkey
from zope.testing.cleanup import addCleanUp
def unsendEvents(class_):
"""Restore class's initial state with respect to sending events"""
for name in ['manage_afterAdd', 'manage_beforeDelete']:
killMonkey(class_, name, '__five_original_'+name)
def cleanUp():
for class_ in _monkied:
unsendEvents(class_)
addCleanUp(cleanUp)
del addCleanUp
......@@ -15,19 +15,26 @@
These directives are specific to Five and have no equivalents in Zope 3.
$Id: fiveconfigure.py 12915 2005-05-31 10:23:19Z philikon $
$Id: fiveconfigure.py 17361 2005-09-08 12:13:30Z regebro $
"""
import os
import sys
import glob
import warnings
import App
from zLOG import LOG, ERROR
from zope.interface import classImplements
from zope.configuration import xmlconfig
from zope.app.component.interface import provideInterface
from viewable import Viewable
from traversable import Traversable
from bridge import fromZ2Interface
from browserconfigure import page
from browser.metaconfigure import page
debug_mode = App.config.getConfiguration().debug_mode
def findProducts():
import Products
......@@ -39,6 +46,17 @@ def findProducts():
products.append(product)
return products
def handleBrokenProduct(product):
if debug_mode:
# Just reraise the error and let Zope handle it.
raise
# Not debug mode. Zope should continue to load. Print a log message:
# XXX It would be really cool if we could make this product appear broken
# in the control panel. However, all attempts to do so has failed from my
# side. //regebro
exc = sys.exc_info()
LOG('Five', ERROR, 'Could not import Product %s' % name, error=exc)
def loadProducts(_context):
products = findProducts()
......@@ -46,21 +64,30 @@ def loadProducts(_context):
for product in products:
zcml = os.path.join(os.path.dirname(product.__file__), 'meta.zcml')
if os.path.isfile(zcml):
try:
xmlconfig.include(_context, zcml, package=product)
except: # Yes, really, *any* kind of error.
handleBrokenProduct(product)
# now load their configure.zcml
for product in products:
zcml = os.path.join(os.path.dirname(product.__file__),
'configure.zcml')
if os.path.isfile(zcml):
try:
xmlconfig.include(_context, zcml, package=product)
except: # Yes, really, *any* kind of error.
handleBrokenProduct(product)
def loadProductsOverrides(_context):
for product in findProducts():
zcml = os.path.join(os.path.dirname(product.__file__),
'overrides.zcml')
if os.path.isfile(zcml):
try:
xmlconfig.includeOverrides(_context, zcml, package=product)
except: # Yes, really, *any* kind of error.
handleBrokenProduct(product)
def implements(_context, class_, interface):
for interface in interface:
......@@ -79,6 +106,7 @@ def implements(_context, class_, interface):
def isFiveMethod(m):
return hasattr(m, '__five_method__')
_traversable_monkies = []
def classTraversable(class_):
# If a class already has this attribute, it means it is either a
# subclass of Traversable or was already processed with this
......@@ -104,6 +132,8 @@ def classTraversable(class_):
setattr(class_, '__bobo_traverse__',
Traversable.__bobo_traverse__.im_func)
setattr(class_, '__five_traversable__', True)
# remember class for clean up
_traversable_monkies.append(class_)
def traversable(_context, class_):
_context.action(
......@@ -112,6 +142,7 @@ def traversable(_context, class_):
args = (class_,)
)
_defaultviewable_monkies = []
def classDefaultViewable(class_):
# If a class already has this attribute, it means it is either a
# subclass of DefaultViewable or was already processed with this
......@@ -136,6 +167,8 @@ def classDefaultViewable(class_):
setattr(class_, '__browser_default__',
Viewable.__browser_default__.im_func)
setattr(class_, '__five_viewable__', True)
# remember class for clean up
_defaultviewable_monkies.append(class_)
def defaultViewable(_context, class_):
_context.action(
......@@ -200,4 +233,44 @@ def pagesFromDirectory(_context, directory, module, for_=None,
page(_context, name=name, permission=permission,
layer=layer, for_=for_, template=fname)
# clean up code
def killMonkey(class_, name, fallback, attr=None):
"""Die monkey, die!"""
method = getattr(class_, name, None)
if isFiveMethod(method):
original = getattr(class_, fallback, None)
if original is None:
try:
delattr(class_, name)
except AttributeError:
pass
else:
setattr(class_, name, original)
if attr is not None:
try:
delattr(class_, attr)
except (AttributeError, KeyError):
pass
def untraversable(class_):
"""Restore class's initial state with respect to traversability"""
killMonkey(class_, '__bobo_traverse__', '__fallback_traverse__',
'__five_traversable__')
def undefaultViewable(class_):
"""Restore class's initial state with respect to being default
viewable."""
killMonkey(class_, '__browser_default__', '__fallback_default__',
'__five_viewable__')
def cleanUp():
for class_ in _traversable_monkies:
untraversable(class_)
for class_ in _defaultviewable_monkies:
undefaultViewable(class_)
from zope.testing.cleanup import addCleanUp
addCleanUp(cleanUp)
del addCleanUp
......@@ -13,7 +13,7 @@
##############################################################################
"""Five ZCML directive schemas
$Id: fivedirectives.py 12915 2005-05-31 10:23:19Z philikon $
$Id: fivedirectives.py 12884 2005-05-30 13:10:41Z philikon $
"""
from zope.interface import Interface
from zope.app.publisher.browser.metadirectives import IBasicResourceInformation
......
......@@ -11,27 +11,17 @@
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Provide basic browser functionality
"""Add and edit views
$Id: browser.py 12915 2005-05-31 10:23:19Z philikon $
$Id: __init__.py 15514 2005-08-02 16:37:34Z yuppie $
"""
# python
import sys
from datetime import datetime
import transaction
# Zope 2
import Acquisition
from Acquisition import aq_inner, aq_parent, aq_base
from AccessControl import ClassSecurityInfo
from Globals import InitializeClass
# Zope 3
from interfaces import ITraversable
from zope.interface import implements
from zope.component import getViewProviding
from zope.app.traversing.browser.interfaces import IAbsoluteURL
import transaction
from zope.event import notify
from zope.schema.interfaces import ValidationError
from zope.publisher.browser import isCGI_NAME
from zope.i18n.interfaces import IUserPreferredCharsets
......@@ -40,86 +30,13 @@ from zope.app.location import LocationProxy
from zope.app.form.utility import setUpEditWidgets, applyWidgetsChanges
from zope.app.form.browser.submit import Update
from zope.app.form.interfaces import WidgetsError, MissingInputError
from zope.event import notify
from zope.app.form.utility import setUpWidgets, getWidgetsData
from zope.app.form.interfaces import IInputWidget, WidgetsError
from zope.schema.interfaces import ValidationError
from zope.app.event.objectevent import ObjectCreatedEvent, ObjectModifiedEvent
from zope.app.i18n import ZopeMessageIDFactory as _
# Five
from Products.Five.pagetemplatefile import FivePageTemplateFile
class BrowserView(Acquisition.Explicit):
security = ClassSecurityInfo()
def __init__(self, context, request):
self.context = context
self.request = request
# XXX do not create any methods on the subclass called index_html,
# as this makes Zope 2 traverse into that first!
InitializeClass(BrowserView)
class AbsoluteURL(BrowserView):
"""An adapter for Zope3-style absolute_url using Zope2 methods
(original: zope.app.traversing.browser.absoluteurl)
"""
def __init__(self, context, request):
self.context, self.request = context, request
implements(IAbsoluteURL)
def __str__(self):
context = aq_inner(self.context)
return context.absolute_url()
__call__ = __str__
def breadcrumbs(self):
context = self.context.aq_inner
container = context.aq_parent
request = self.request
name = context.getId()
if container is None or self._isVirtualHostRoot() \
or not ITraversable.providedBy(container):
return (
{'name': name, 'url': context.absolute_url()},)
view = getViewProviding(container, IAbsoluteURL, request)
base = tuple(view.breadcrumbs())
base += (
{'name': name, 'url': ("%s/%s" % (base[-1]['url'], name))},)
return base
def _isVirtualHostRoot(self):
virtualrootpath = self.request.get('VirtualRootPhysicalPath', None)
if virtualrootpath is None:
return False
context = self.context.aq_inner
return context.restrictedTraverse(virtualrootpath) == context
class SiteAbsoluteURL(AbsoluteURL):
"""An adapter for Zope3-style absolute_url using Zope2 methods
This one is just used to stop breadcrumbs from crumbing up
to the Zope root.
(original: zope.app.traversing.browser.absoluteurl)
"""
def breadcrumbs(self):
context = self.context
request = self.request
return ({'name': context.getId(),
'url': context.absolute_url()
},)
from Products.Five.browser import BrowserView
from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile
class EditView(BrowserView):
"""Simple edit-view base class
......@@ -136,7 +53,7 @@ class EditView(BrowserView):
# Fall-back field names computes from schema
fieldNames = property(lambda self: getFieldNamesInOrder(self.schema))
# Fall-back template
generated_form = FivePageTemplateFile('edit.pt')
generated_form = ZopeTwoPageTemplateFile('edit.pt')
def __init__(self, context, request):
BrowserView.__init__(self, context, request)
......@@ -218,7 +135,7 @@ class EditView(BrowserView):
notify(ObjectModifiedEvent(content))
except WidgetsError, errors:
self.errors = errors
status = "An error occured."
status = _("An error occured.")
transaction.abort()
else:
setUpEditWidgets(self, self.schema, source=self.adapted,
......@@ -226,13 +143,13 @@ class EditView(BrowserView):
names=self.fieldNames)
if changed:
self.changed()
# XXX: Needs i18n support:
# XXX: Needs locale support:
# formatter = self.request.locale.dates.getFormatter(
# 'dateTime', 'medium')
# status = _("Updated on ${date_time}")
status = _("Updated on ${date_time}")
# status.mapping = {'date_time': formatter.format(
# datetime.utcnow())}
status = "Updated on %s" % str(datetime.utcnow())
status.mapping = {'date_time': str(datetime.utcnow())}
self.update_status = status
return status
......@@ -260,7 +177,7 @@ class AddView(EditView):
self.createAndAdd(data)
except WidgetsError, errors:
self.errors = errors
self.update_status = "An error occured."
self.update_status = _("An error occured.")
return self.update_status
self.request.response.redirect(self.nextURL())
......@@ -332,3 +249,9 @@ class AddView(EditView):
def nextURL(self):
return self.context.nextURL()
# BBB: Will be removed in future versions
from Products.Five import browser
browser.AddView = AddView
browser.EditView = EditView
<html metal:use-macro="context/@@standard_macros/page">
<html metal:use-macro="context/@@standard_macros/page"
i18n:domain="zope">
<body>
<div metal:fill-slot="body">
......@@ -12,7 +13,7 @@
<h3 tal:condition="view/label"
tal:content="view/label"
metal:define-slot="heading"
>Edit something</h3>
>Add something</h3>
<p tal:define="status view/update"
tal:condition="status"
......@@ -31,7 +32,7 @@
<div class="label"><input type="text" style="width:100%" /></div>
</div>
<div metal:use-macro="context/@@widget_macros/widget_rows" />
<div metal:use-macro="context/@@form_macros/widget_rows" />
<div class="separator"></div>
......@@ -70,3 +71,4 @@
</body>
</html>
<configure xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser">
<include package="zope.app.form.browser" />
<browser:page
for="*"
name="form_macros"
permission="zope2.View"
class=".macros.FormMacros"
allowed_interface="zope.interface.common.mapping.IItemMapping"
/>
</configure>
\ No newline at end of file
<tal:tag condition="view/update"/>
<html metal:use-macro="context/@@standard_macros/page">
<html metal:use-macro="context/@@standard_macros/view"
i18n:domain="zope">
<body>
<div metal:fill-slot="body">
......@@ -33,7 +34,7 @@
<div class="field"><input type="text" style="width:100%" /></div>
</div>
<div metal:use-macro="context/@@widget_macros/widget_rows" />
<div metal:use-macro="context/@@form_macros/widget_rows" />
<div class="separator"></div>
......
##############################################################################
#
# Copyright (c) 2005 Zope Corporation and Contributors. All Rights Reserved.
# 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.
......@@ -10,5 +11,12 @@
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Form macros
# Five.tests.products
$Id: macros.py 12884 2005-05-30 13:10:41Z philikon $
"""
from Products.Five.skin.standardmacros import StandardMacros
# copy of zope.app.form.browser.macros.FormMacros
class FormMacros(StandardMacros):
macro_pages = ('widget_macros', 'addform_macros')
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:meta="http://namespaces.zope.org/meta">
<meta:directives namespace="http://namespaces.zope.org/browser">
<meta:complexDirective
name="editform"
schema="zope.app.form.browser.metadirectives.IEditFormDirective"
handler=".metaconfigure.EditFormDirective"
>
<meta:subdirective
name="widget"
schema="zope.app.form.browser.metadirectives.IWidgetSubdirective"
/>
</meta:complexDirective>
<meta:complexDirective
name="addform"
schema="zope.app.form.browser.metadirectives.IAddFormDirective"
handler=".metaconfigure.AddFormDirective"
>
<meta:subdirective
name="widget"
schema="zope.app.form.browser.metadirectives.IWidgetSubdirective"
/>
</meta:complexDirective>
</meta:directives>
</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.
#
##############################################################################
"""Edit form directives
$Id: metaconfigure.py 12884 2005-05-30 13:10:41Z philikon $
"""
import ExtensionClass
from zope.component import getGlobalService
from zope.component.servicenames import Presentation
from zope.publisher.interfaces.browser import IBrowserRequest
from zope.app.publisher.browser.globalbrowsermenuservice import \
menuItemDirective
from zope.app.form.browser.metaconfigure import BaseFormDirective
from zope.app.container.interfaces import IAdding
from Products.Five.form import EditView, AddView
from Products.Five.metaclass import makeClass
from Products.Five.security import protectClass, initializeClass
from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile
from Products.Five.browser.metaconfigure import makeClassForTemplate
def EditViewFactory(name, schema, label, permission, layer,
template, default_template, bases, for_, fields,
fulledit_path=None, fulledit_label=None, menu=u''):
s = getGlobalService(Presentation)
class_ = makeClassForTemplate(template, globals(), used_for=schema,
bases=bases)
class_.schema = schema
class_.label = label
class_.fieldNames = fields
class_.fulledit_path = fulledit_path
if fulledit_path and (fulledit_label is None):
fulledit_label = "Full edit"
class_.fulledit_label = fulledit_label
class_.generated_form = ZopeTwoPageTemplateFile(default_template)
s.provideView(for_, name, IBrowserRequest, class_, layer)
protectClass(class_, permission)
initializeClass(class_)
class FiveFormDirective(BaseFormDirective):
def _processWidgets(self):
if self._widgets:
customWidgetsObject = makeClass('CustomWidgetsMixin', (ExtensionClass.Base,), self._widgets)
self.bases = self.bases + (customWidgetsObject,)
class EditFormDirective(FiveFormDirective):
view = EditView
default_template = 'edit.pt'
title = 'Edit'
def _handle_menu(self):
if self.menu:
menuItemDirective(
self._context, self.menu, self.for_ or self.schema,
'@@' + self.name, self.title, permission=self.permission)
def __call__(self):
self._processWidgets()
self._handle_menu()
self._context.action(
discriminator=self._discriminator(),
callable=EditViewFactory,
args=self._args(),
kw={'menu': self.menu},
)
def AddViewFactory(name, schema, label, permission, layer,
template, default_template, bases, for_,
fields, content_factory, arguments,
keyword_arguments, set_before_add, set_after_add,
menu=u''):
s = getGlobalService(Presentation)
class_ = makeClassForTemplate(template, globals(), used_for=schema,
bases=bases)
class_.schema = schema
class_.label = label
class_.fieldNames = fields
class_._factory = content_factory
class_._arguments = arguments
class_._keyword_arguments = keyword_arguments
class_._set_before_add = set_before_add
class_._set_after_add = set_after_add
class_.generated_form = ZopeTwoPageTemplateFile(default_template)
s.provideView(for_, name, IBrowserRequest, class_, layer)
protectClass(class_, permission)
initializeClass(class_)
class AddFormDirective(FiveFormDirective):
view = AddView
default_template = 'add.pt'
for_ = IAdding
# default add form information
description = None
content_factory = None
arguments = None
keyword_arguments = None
set_before_add = None
set_after_add = None
def _handle_menu(self):
if self.menu or self.title:
if (not self.menu) or (not self.title):
raise ValueError("If either menu or title are specified, "
"they must both be specified")
# Add forms are really for IAdding components, so do not use
# for=self.schema.
menuItemDirective(
self._context, self.menu, self.for_, '@@' + self.name,
self.title, permission=self.permission,
description=self.description)
def _handle_arguments(self, leftover=None):
schema = self.schema
fields = self.fields
arguments = self.arguments
keyword_arguments = self.keyword_arguments
set_before_add = self.set_before_add
set_after_add = self.set_after_add
if leftover is None:
leftover = fields
if arguments:
missing = [n for n in arguments if n not in fields]
if missing:
raise ValueError("Some arguments are not included in the form",
missing)
optional = [n for n in arguments if not schema[n].required]
if optional:
raise ValueError("Some arguments are optional, use"
" keyword_arguments for them",
optional)
leftover = [n for n in leftover if n not in arguments]
if keyword_arguments:
missing = [n for n in keyword_arguments if n not in fields]
if missing:
raise ValueError(
"Some keyword_arguments are not included in the form",
missing)
leftover = [n for n in leftover if n not in keyword_arguments]
if set_before_add:
missing = [n for n in set_before_add if n not in fields]
if missing:
raise ValueError(
"Some set_before_add are not included in the form",
missing)
leftover = [n for n in leftover if n not in set_before_add]
if set_after_add:
missing = [n for n in set_after_add if n not in fields]
if missing:
raise ValueError(
"Some set_after_add are not included in the form",
missing)
leftover = [n for n in leftover if n not in set_after_add]
self.set_after_add += leftover
else:
self.set_after_add = leftover
def __call__(self):
self._processWidgets()
self._handle_menu()
self._handle_arguments()
self._context.action(
discriminator=self._discriminator(),
callable=AddViewFactory,
args=self._args()+(self.content_factory, self.arguments,
self.keyword_arguments,
self.set_before_add, self.set_after_add),
kw={'menu': self.menu},
)
<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"
xmlns:i18n="http://namespaces.zope.org/i18n"
i18n_domain="formtest">
<!-- make the zope2.Public permission work -->
<meta:redefinePermission from="zope2.Public" to="zope.Public" />
<!-- browser forms -->
<five:traversable class=".schemacontent.FieldContent" />
<browser:editform
schema=".schemacontent.IFieldContent"
for=".schemacontent.IFieldContent"
name="edit.html"
label="Edit Field Content"
permission="zope2.Public"
/>
<five:traversable class=".schemacontent.ComplexSchemaContent" />
<browser:editform
schema=".schemacontent.IComplexSchemaContent"
for=".schemacontent.IComplexSchemaContent"
name="edit.html"
permission="zope2.Public"
class=".schemacontent.ComplexSchemaView"
/>
<view
type="zope.publisher.interfaces.browser.IBrowserRequest"
for="zope.schema.interfaces.IObject"
provides="zope.app.form.interfaces.IInputWidget"
factory="zope.app.form.browser.objectwidget.ObjectWidget"
permission="zope.Public"
/>
<!-- With a widget override -->
<browser:editform
schema=".schemacontent.IFieldContent"
for=".schemacontent.IFieldContent"
name="widgetoverride.html"
permission="zope2.Public"
>
<widget
field="description"
class="zope.app.form.browser.TextAreaWidget"
/>
</browser:editform>
<browser:addform
schema=".schemacontent.IFieldContent"
content_factory=".schemacontent.FieldContent"
name="addfieldcontent.html"
label="Add Field Content"
permission="zope2.Public"
/>
<browser:addform
schema=".schemacontent.IFieldContent"
content_factory=".schemacontent.FieldContent"
name="addwidgetoverride.html"
permission="zope2.Public">
<widget
field="description"
class="zope.app.form.browser.TextAreaWidget"
/>
</browser:addform>
<browser:addform
schema=".schemacontent.IFieldContent"
content_factory=".schemacontent.FieldContent"
name="protectedaddform.html"
permission="zope2.ViewManagementScreens"
/>
<i18n:registerTranslations directory="locales"/>
</configure>
Testing forms
=============
Before we can begin, we need to setup a traversable folder.
Otherwise, Five won't get to to do its view lookup:
Before we can begin, we need to set up a few things. We need a
manager account:
>>> from Products.Five.tests.products.FiveTest.helpers import \
... manage_addFiveTraversableFolder
>>> uf = self.folder.acl_users
>>> uf._doAddUser('manager', 'r00t', ['Manager'], [])
We need to configure all of Five for the functional test:
>>> import Products.Five.form.tests
>>> from Products.Five import zcml
>>> zcml.load_config('configure.zcml', package=Products.Five)
>>> zcml.load_config('configure.zcml', package=Products.Five.form.tests)
Finally, we need to setup a traversable folder. Otherwise, Five won't
get to to do its view lookup:
>>> from Products.Five.testing import manage_addFiveTraversableFolder
>>> manage_addFiveTraversableFolder(self.folder, 'ftf')
......@@ -30,15 +42,14 @@ For a protected one we need a manager account:
HTTP/1.1 200 OK
...
otherwise we will fail to access it:
otherwise we will fail to access it (N.B: this test will fail on Zope 2.8.1,
which incorrectly ignored its 'handle_errors' argument):
>>> print http(r"""
... GET /test_folder_1_/ftf/+/protectedaddform.html HTTP/1.1
... Authorization: Basic viewer:secret
... """, handle_errors=False)
Traceback (most recent call last):
... """, handle_errors=True)
HTTP/1.1 401 Unauthorized
...
Unauthorized: ...
Now let's add a piece of our sample content object to test more things
on it:
......@@ -76,7 +87,6 @@ Having added this piece of content, we can access it under its URL:
>>> print http(r"""
... GET /test_folder_1_/ftf/edittest HTTP/1.1
... Authorization: Basic manager:r00t
... """, handle_errors=False)
HTTP/1.1 200 OK
...
......@@ -411,13 +421,14 @@ The object's data will have changed accordingly:
>>> unicodetest.somelist == [de_guo.decode('utf-8')]
True
Object widget:
--------------
A little more complex is the ``ObjectWidget``. Here we simply test
that the edit form works:
>>> from Products.Five.tests.products.FiveTest.schemacontent import \
>>> from Products.Five.form.tests.schemacontent import \
... manage_addComplexSchemaContent
>>> n = manage_addComplexSchemaContent(self.folder.ftf, 'objecttest')
......@@ -426,3 +437,140 @@ that the edit form works:
... """, handle_errors=False)
HTTP/1.1 200 OK
...
i18n:
-----
And now the add form in German:
>>> print http(r"""
... GET /test_folder_1_/ftf/+/addfieldcontent.html HTTP/1.1
... Accept-Language: de
... Authorization: Basic manager:r00t
... """, handle_errors=False)
HTTP/1.1 200 OK
...Felderinhalt hinzuf...
...Eine kurz...Titel...
...Eine ausf...Beschreibung...
...Irgendeine Zahl...
...Irgendeine Liste...
...hinzuf...
...Auffrischen...
...Hinzuf...
...Objektname...
The same with an input error:
>>> print http(r"""
... POST /test_folder_1_/ftf/+/addfieldcontent.html HTTP/1.1
... Accept-Language: de
... Authorization: Basic manager:r00t
... Content-Length: 670
... Content-Type: multipart/form-data; boundary=---------------------------19588947601368617292863650127
...
... -----------------------------19588947601368617292863650127
... Content-Disposition: form-data; name="field.title"
...
...
... -----------------------------19588947601368617292863650127
... Content-Disposition: form-data; name="field.description"
...
...
... -----------------------------19588947601368617292863650127
... Content-Disposition: form-data; name="field.somenumber"
...
... 0
... -----------------------------19588947601368617292863650127
... Content-Disposition: form-data; name="UPDATE_SUBMIT"
...
... Hinzufxgen
... -----------------------------19588947601368617292863650127
... Content-Disposition: form-data; name="add_input_name"
...
...
... -----------------------------19588947601368617292863650127--
... """, handle_errors=False)
HTTP/1.1 200 OK
...Felderinhalt hinzuf...
...Ein Fehler ist aufgetreten...
...Es gab <strong>1</strong> Eingabefehler...
...Eine kurz...Titel...
...Erforderliche Eingabe fehlt...
...Eine ausf...Beschreibung...
...Irgendeine Zahl...
...Irgendeine Liste...
...hinzuf...
...Auffrischen...
...Hinzuf...
...Objektname...
And now the translated edit form:
>>> from Products.Five.form.tests.schemacontent import \
... manage_addFieldContent
>>> dummy = manage_addFieldContent(self.folder.ftf, 'i18ntest', 'titel')
>>> print http(r"""
... GET /test_folder_1_/ftf/i18ntest/edit.html HTTP/1.1
... Accept-Language: de
... Authorization: Basic manager:r00t
... """, handle_errors=False)
HTTP/1.1 200 OK
...Felderinhalt bearbeiten...
...Eine kurz...Titel...
...Eine ausf...Beschreibung...
...Irgendeine Zahl...
...Irgendeine Liste...
...hinzuf...
...Auffrischen...
...Abschicken...
Again with an input error:
>>> print http(r"""
... POST /test_folder_1_/ftf/i18ntest/edit.html HTTP/1.1
... Accept-Language: de
... Authorization: Basic manager:r00t
... Content-Length: 550
... Content-Type: multipart/form-data; boundary=---------------------------13070562555632576681565633754
...
... -----------------------------13070562555632576681565633754
... Content-Disposition: form-data; name="field.title"
...
...
... -----------------------------13070562555632576681565633754
... Content-Disposition: form-data; name="field.description"
...
...
... -----------------------------13070562555632576681565633754
... Content-Disposition: form-data; name="field.somenumber"
...
... 0
... -----------------------------13070562555632576681565633754
... Content-Disposition: form-data; name="UPDATE_SUBMIT"
...
... Abschicken
... -----------------------------13070562555632576681565633754--
... """, handle_errors=False)
HTTP/1.1 200 OK
...Felderinhalt bearbeiten...
...Ein Fehler ist aufgetreten...
...Es gab <strong>1</strong> Eingabefehler...
...Eine kurz...Titel...
...Erforderliche Eingabe fehlt...
...Eine ausf...Beschreibung...
...Irgendeine Zahl...
...Irgendeine Liste...
...hinzuf...
...Auffrischen...
...Abschicken...
Clean up
--------
Finally, we need to clean up:
>>> from zope.app.tests.placelesssetup import tearDown
>>> tearDown()
##############################################################################
#
# 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()
##############################################################################
#
# 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.
#
##############################################################################
msgid ""
msgstr ""
"Project-Id-Version: Five form tests\n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"Language-Team: Five Developers <z3-five@zope.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
msgid "Title"
msgstr ""
msgid "A short description of the event."
msgstr ""
msgid "Description"
msgstr ""
msgid "A long description of the event."
msgstr ""
msgid "Some number"
msgstr ""
msgid "Some List"
msgstr ""
msgid "Some item"
msgstr ""
msgid "Edit Field Content"
msgstr ""
msgid "Add Field Content"
msgstr ""
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.
<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>
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