vendor/Five/1.2b-r20590
changeset 0:3673ed425f80 1.2b-r20590 tip
Vendor import of Five 1.2b+ (r20590)
line diff
1.1 new file mode 100644 1.2 --- /dev/null 1.3 +++ b/CHANGES.txt 1.4 @@ -0,0 +1,449 @@ 1.5 +============ 1.6 +Five Changes 1.7 +============ 1.8 + 1.9 +Five 1.3c (unreleased) 1.10 +====================== 1.11 + 1.12 +This version is also included in Zope 2.9b1. 1.13 + 1.14 +Restructuring 1.15 +------------- 1.16 + 1.17 +* (b6) Added ``original`` parameter to ObjectCopiedEvent backported from 1.18 + Zope 3.2 1.19 + 1.20 +* (b4) Cleaned up security test. 1.21 + 1.22 +* (b4) Made Five send a ContainerModifiedEvent when appropriate. 1.23 + 1.24 +Bugfixes 1.25 +-------- 1.26 + 1.27 +* (b6) Added backward compatibility for old _setObject and _delObject 1.28 + implementations without suppress_events. 1.29 + 1.30 +* (b3) Made the creation of custom skins work again. It was broken in 1.31 + the port to Zope 3.2. 1.32 + 1.33 +* (b2) Fixed bug that broke WebDAV access for five:defaultViewable 1.34 + objects. The __browser_default__ now modifies only GET and POST 1.35 + requests. 1.36 + 1.37 +* (b2) Fixed some event recursion compatibility modes. 1.38 + 1.39 +Five 1.3b2 (2005-11-10) 1.40 +======================= 1.41 + 1.42 +This version is also included in Zope 2.9b1. 1.43 + 1.44 +Changes compared to Five 1.3b: 1.45 + 1.46 +Bugfixes 1.47 +-------- 1.48 + 1.49 +* Fixed bug that broke WebDAV access for five:defaultViewable objects. The 1.50 + __browser_default__ now modifies only GET and POST requests. 1.51 + 1.52 +* Fixed some event recursion compatibility modes. 1.53 + 1.54 +Five 1.3b (2005-11-02) 1.55 +====================== 1.56 + 1.57 +Restructuring 1.58 +------------- 1.59 + 1.60 +* Support for Zope 3.2 was added. Five now requires Zope 2.9 (which 1.61 + ships with Zope 3.2). 1.62 + 1.63 +* As scheduled, the temporary fork of the new test runner 1.64 + (``zope.testing``) at ``Five.testing`` was removed. So was the 1.65 + ``runtests.py`` script. Use the regular Zope test runner 1.66 + (``test.py`` or ``bin/zopectl test``) to run tests. 1.67 + 1.68 +* To reflect the Component Architecture simplification in Zope 3 since 1.69 + the X3 3.0 release, ``IFiveUtilityService`` was renamed to 1.70 + ``IFiveUtilityRegistry`` and ``SimpleLocalUtilityService`` was 1.71 + renamed to ``SimpleLocalUtilityRegistry``. The old names are still 1.72 + available for a short period of time. 1.73 + 1.74 +* Event support: ``<five:containerEvents/>`` is the default. 1.75 + 1.76 +* Due to an incompatability with Zope 3.2's ObjectWidget and Zope 2's 1.77 + Page Templates, Five now ships with its own ObjectWidget 1.78 + implementation (which is just a thin wrapper around Zope's one to 1.79 + make it work in Zope 2). If you use the ObjectWidget, please change 1.80 + your imports to ``Products.Five.form.objectwidget.ObjectWidget``. 1.81 + 1.82 +* Backwards compatability for Zope 3-style interfaces of Zope 2 1.83 + components has been removed as that functionality is now in the Zope 1.84 + 2 core as of Zope 2.9. 1.85 + 1.86 +Five 1.2 (unreleased) 1.87 +===================== 1.88 + 1.89 +Bugfixes 1.90 +-------- 1.91 + 1.92 +* Fixed bug that broke WebDAV access for five:defaultViewable objects. The 1.93 + __browser_default__ now modifies only GET and POST requests. 1.94 + 1.95 +* Fixed some event recursion compatibility modes. 1.96 + 1.97 +* Fixed loops in zcml loading due to events in some cases. 1.98 + 1.99 +* Made Five send a ContainerModifiedEvent when appropriate. 1.100 + 1.101 +Restructuring 1.102 +------------- 1.103 + 1.104 +* Cleaned up security test. 1.105 + 1.106 +* Added monkey so that ++skin++ works with Zope <= 2.8.4. 1.107 + 1.108 +Five 1.2b (2005-11-02) 1.109 +====================== 1.110 + 1.111 +Features 1.112 +-------- 1.113 + 1.114 +* Added IMarkerInterfaces adapter: This adapter provides methods for 1.115 + inspecting and assigning marker interfaces. 'edit-markers.html' (or 1.116 + 'manage_interfaces' in the ZMI) allows to change the behavior of specific 1.117 + objects by adding or removing marker interfaces TTW. 1.118 + 1.119 +* Added the five:registerClass directive: This does the necessary Zope 2 1.120 + registration for Five-based content. It is no longer necessary to add an 1.121 + ``initialize()`` function to the product's __init__ in order to register 1.122 + a meta type to be addable through the ZMI. See doc/products/ViewsTutorial 1.123 + for an example how to use the directive. 1.124 + 1.125 +* Local site support: Five has now support for creating local sites 1.126 + and thereby local utilities. This is mostly needed for allowing CMF 1.127 + to convert it's portal tools into local utilities. See 1.128 + doc/localsite.txt for more information 1.129 + 1.130 +* Event support: When ``<five:containerEvents/>`` is specified, Five 1.131 + makes the standard Zope 2 containers send events instead of using 1.132 + manage_afterAdd, manage_beforeDelete and manage_afterClone. These 1.133 + methods are still called for a class declared 1.134 + ``<five:deprecatedManageAddDelete class=.../>``, and are called in 1.135 + compatibility mode with a deprecation warning for classes that don't 1.136 + use this directive. 1.137 + 1.138 +Restructuring 1.139 +------------- 1.140 + 1.141 +* Removed backwards compatibility for Five 1.0 Zope core interfaces. 1.142 + 1.143 +* Removed backwards compatibility for Zope 2.7 and 2.8.0. 1.144 + 1.145 +* Added a (temporarily) forked copy of the "new-and-improved" test 1.146 + runner and supporting ``zope.testing`` package, lifted from 1.147 + http://svn.zope.org/zope.testing. This code should be removed for 1.148 + Five 1.3, which will use the updated version of ``zope.testing`` in 1.149 + the Zope 2.9 / Zope 3.2 tree. 1.150 + 1.151 + There is a test runner invoking script in the ``Five`` package. For 1.152 + example, to run the Five tests with the new test runner, simply 1.153 + execute the following command line from your instance home:: 1.154 + 1.155 + $ bin/zopectl run Products/Five/runtests.py -v -s Products.Five 1.156 + 1.157 +* Moved the ``Five.testing`` package down to ``Five.tests.testing``, 1.158 + in order to make room for the 'zope.testing' code. 1.159 + 1.160 +* Removed backwards compatibility for some moved classes (AddForm, 1.161 + EditForm, ContentAdding) 1.162 + 1.163 +Five 1.1 (2005-10-04) 1.164 +===================== 1.165 + 1.166 +Features 1.167 +-------- 1.168 + 1.169 +* When Zope was not in debug mode, an error in a ZCML file would cause Five to 1.170 + stop loading ZCML completely, making all subsequent products "dead". The 1.171 + effect would typically be that objects appeared to have no views at all. 1.172 + Now a ZCML error will only stop the ZCML loading for that product, but the 1.173 + rest of the products will load as usual. A traceback will still be printed 1.174 + in the event log. 1.175 + 1.176 + In debug mode the behaviour has not changed; a ZCML error will stop Zope 1.177 + startup completely, and print a traceback if running in foreground mode. 1.178 + 1.179 +Restructuring 1.180 +------------- 1.181 + 1.182 +* The deprecated FivePageTemplateFile was removed, and the erroneous use of 1.183 + this by EditView was changed. 1.184 + 1.185 +Bugfixes 1.186 +-------- 1.187 + 1.188 +* Repaired 'forms.txt' test which expected an error page when passing 1.189 + 'handle_errors' as False; it now expects an Unauthorized traceback. 1.190 + Note that this test fails on Zope 2.8.1, which incorrectly ignored 1.191 + 'handle_errors'. 1.192 + 1.193 +* FiveTraversable should only do a view lookup and not call the traverse 1.194 + method of its superclass. 1.195 + 1.196 +* Fixed manage_beforeDelete triggering for classes using five:sendEvents. 1.197 + 1.198 +* The redefinePermission directive was falsely registered under the 1.199 + ``zope`` namespace, not the ``meta`` namespace as it is in Zope 3. 1.200 + 1.201 +* Some parts of add.pt and edit.pt were not translated correctly or not 1.202 + translated at all. The fix depends on TAL changes in Zope 2.8.1 and changes 1.203 + in Zope X3-3.0.1 (shipped with 2.8.1). Form i18n is still broken with older 1.204 + Zope versions. 1.205 + 1.206 +* 'zope' domain translations are now set up by default. Form i18n needs them. 1.207 + 1.208 +* Added backwards compatibility for some moved classes (AddForm, EditForm, 1.209 + ContentAdding) 1.210 + 1.211 +* The ZPT variable 'container' makes little sense in Zope3/Five, but is now 1.212 + always set to be the same as 'here' which is normal Zope2 behaviour. 1.213 + It is in Five 1.0.x set to be the same as 'view' which breaks some templates. 1.214 + 1.215 +* In some hard to replicate cases, using the "modules" variable in ZPT cause 1.216 + an AuthenticationError. Using the secure module importer fixes this. 1.217 + 1.218 +* If you used some parts of Zope 3 (for example the mail delivery) Five 1.1 1.219 + transaction backport would conflict with Zope 3s transaction module. 1.220 + This is now solved. 1.221 + 1.222 +Five 1.1b (2005-07-13) 1.223 +====================== 1.224 + 1.225 +Features 1.226 +-------- 1.227 + 1.228 +* Zope 3-style i18n support has been provided. Apart from being able 1.229 + to register translations through ZCML, Five now lets Zope 2 ZPTs 1.230 + automatically use Zope 3 translation domains. Fallback to an 1.231 + old-style translation service (e.g. Localizer or PTS) is supported. 1.232 + This also includes the detection of preferred languages. See 1.233 + ``doc/i18n.txt`` for more information. 1.234 + 1.235 +* Added support for Zope 3 -> Zope 2 interface bridging. This 1.236 + functionality will be part of Zope 2.9, with Five you can already 1.237 + use it in Zope 2.7/2.8. Since Zope 2 interfaces are rarely used and 1.238 + their Zope 3 equivalents are more meaningful (for the Component 1.239 + Architecture), the preferred way of dealing with interface migration 1.240 + is to write Zope 3 interfaces and bridge them to Zope 2 ones as 1.241 + needed. To bridge, use the ``Interface.bridge.fromZ3Interface()`` 1.242 + function. 1.243 + 1.244 +* Support for the standard <factory />, <modulealias /> and <hook /> 1.245 + ZCML directives was added. 1.246 + 1.247 +* The default browser view name for all objects is now 'index.html', 1.248 + just as it is in Zope 3. This means that a view by that name will 1.249 + be looked up if no specific view name is given in the URL. 1.250 + 1.251 +Restructuring 1.252 +------------- 1.253 + 1.254 +* Restructured the Five source code to be easier to navigate in. 1.255 + Three subpackages were created, Five.browser, Five.form and 1.256 + Five.skin. 1.257 + 1.258 +* The former test product, ``FiveTest``, was converted into separate 1.259 + modules that provide the mock objects for the corresponding tests 1.260 + and are located right next to them. Common test helpers have been 1.261 + moved to the Five.testing package. Overall, the testing framework 1.262 + was much simplified and the individual tests clean up after 1.263 + themselves much like they do in Zope 3. 1.264 + 1.265 +* Relocated Zope core interfaces. Future Zope versions will ship with their 1.266 + own z3 interfaces. Five now patches the older Zope versions to make sure 1.267 + you can always find the interfaces in 'AccessControl.interfaces', 1.268 + 'Acquisition.interfaces', 'App.interfaces', 'OFS.interfaces' and 1.269 + 'webdav.interfaces'. Please don't use the aliases in 'Five.interfaces' or 1.270 + 'Five.bbb.*interfaces' - they are only provided for backwards 1.271 + compatibility. 1.272 + 1.273 +* Zope 2.8 HTTPRequest is no longer patched. It has the required methods. 1.274 + 1.275 +Bugfixes 1.276 +-------- 1.277 + 1.278 +* The ZPT variable 'container' did not always contain the parent object 1.279 + of the context. 1.280 + 1.281 +* The deprecated get_transaction method is no longer used in Zope 2.8. 1.282 + 1.283 +Five 1.0.2 (2005-07-12) 1.284 +======================= 1.285 + 1.286 +This version is also included in Zope 2.8.1 1.287 + 1.288 +* Fixed some issues with bridged interfaces: Bases and Methods were not 1.289 + bridged correctly. extends() was never True. 1.290 + 1.291 +* zope.security.checkPermission now behaves exactly like 1.292 + Five.security.checkPermission (in fact, the former now calls the 1.293 + latter through the indirection of Zope 3 security policies). 1.294 + 1.295 +* Fixed a bug with resource directories. Resources within those were 1.296 + not rendering their absolute URL correctly. 1.297 + 1.298 +Five 1.0.1 (2005-05-31) 1.299 +======================= 1.300 + 1.301 +This version is also included in Zope 2.8.0 1.302 + 1.303 +* Changed license headers to the ones used in the Zope.org repository. 1.304 + This makes merging between the main development line of Five (hosted 1.305 + on codespeak.net) and the version integrated into Zope 2.8 much 1.306 + easier. The actual copyright ownership isn't affected because Five 1.307 + had been contributed to the Zope project anyway (which was blessed 1.308 + by all Five contributors). 1.309 + 1.310 +* Made automatically generated add and edit forms unicode-aware. 1.311 + ZPublisher does not automatically decode incoming form values to 1.312 + unicode, so AddView and EditView emulate this behaviour themselves 1.313 + now. They also take care of setting the right charset on the 1.314 + outgoing form so that ZPublisher will encode it accordingly when 1.315 + sending the response to the client. (In Zope 3, all charset 1.316 + negotation between the client and the server takes place in the 1.317 + publisher.) 1.318 + 1.319 +* Added ``IHTTPCharset`` adapter for ``IHTTPRequest`` so that 1.320 + application can find out the preferred character set of the HTTP 1.321 + client (Zope 2 applications needs to take care of their own charset 1.322 + header). The adapter is used for the automatically-generated forms 1.323 + when determining encodings for unicode field content. 1.324 + 1.325 +* Modified edit.pt to make sure editforms have only one body tag. 1.326 + 1.327 +* Fixed the ``INameChooser`` adapter for ObjectManagers (e.g. Zope 2 1.328 + folders) and added unit tests. 1.329 + 1.330 +* Fixed small bug in BrowserDefault which caused an error if the class is 1.331 + defaultViewable but the object's interfaces have no defaultView. 1.332 + 1.333 +Five 1.0 (2005-04-27) 1.334 +===================== 1.335 + 1.336 +Features 1.337 +-------- 1.338 + 1.339 +* Zope 3 style ``ISized`` adapters for objects are now exposed to the 1.340 + ZMI and other Zope 2 frameworks via the known ``get_size`` method, 1.341 + provided this is turned for the class in question via the 1.342 + five:sizable ZCML directive. 1.343 + 1.344 +* There is now a standard standard_macros. Five page templates can use 1.345 + context/@@standard_macros/view to get the default site layout, and 1.346 + people can register their own standard_macros in a skin. 1.347 + 1.348 +* The addform and editform directive now supports the widget ZCML 1.349 + subdirective, which previously was ignored. 1.350 + 1.351 +* Five now supports the vocabulary ZCML directive. 1.352 + 1.353 +Bugfixes 1.354 +-------- 1.355 + 1.356 +* Add and edit forms are now protected properly. 1.357 + 1.358 +* The checkbox widget did not work correctly in its off state, this 1.359 + has been fixed. 1.360 + 1.361 +Five 0.3 (2005-03-11) 1.362 +===================== 1.363 + 1.364 +* Five now uses the Zope 2 page template engine, not the Zope 3 1.365 + engine. This allows better integration with Zope 2-based page 1.366 + templates, such as macros. 1.367 + 1.368 + It uses TrustedExecutables technology (thanks to Dieter Maurer) to 1.369 + turn off Zope 2 security in page templates, so Five's security 1.370 + behavior is very similar to what it was before. 1.371 + 1.372 +* Five now supports the browser:menu, menuItem and menuItems 1.373 + directives. 1.374 + 1.375 +* A new Five-specific directive has been added: 1.376 + five:pagesFromDirectory. This adds one page for each .pt file in a 1.377 + directory to the specified interface. This is useful for Five 1.378 + integration with CMF and other systems that have Page Templates 1.379 + macros that need to be shared between Zope2 and Five. 1.380 + 1.381 +* Five.security.checkPermission has been changed from a (unused) 1.382 + method for checking the existence of permissions. Use 1.383 + zope.app.security.permission.checkPermission if you need that 1.384 + functionality. 1.385 + 1.386 + Instead Five.security.checkPermission is now a Five version of 1.387 + zope.security.checkPermission, which checks if the current user has 1.388 + a permission on an object. 1.389 + 1.390 +* Support for browser:editform. You can now use schemas for editing. 1.391 + 1.392 +* Support for browser:addform; add forms using '+'. You can now browse 1.393 + to 'container/+/addsomething.html' to get to a schema-driven add 1.394 + form. 1.395 + 1.396 +* Fixed a traversal bug which caused Zope to give the wrong error when 1.397 + a page could not be found (missing docstring instead of not 1.398 + found). Zope 2.7.4 (or higher) is required for this fix. 1.399 + 1.400 +Five 0.2b (2004-09-24) 1.401 +====================== 1.402 + 1.403 +* Added utility module, 'bridge', allowing reuse of Zope 2 interfaces 1.404 + (by introspecting them to create equivalent Zope 3 interfaces). 1.405 + 1.406 +* five:viewable was renamed to five:traversable, five:viewable still 1.407 + works but is deprecated; a deprecation warning is emitted when it is 1.408 + used. 1.409 + 1.410 +* like in Zope3, an ITraverser adapter is looked up to determine what 1.411 + happens when traversing into a Five traversable object. 1.412 + 1.413 +* added five:defaultViewable to make instances of a class directly 1.414 + viewable using browser:defaultView. This is hookable by the use of a 1.415 + IBrowserDefault adapter 1.416 + 1.417 +* deprecated use of Products.Five.api as public API for other products 1.418 + to use, instead import directly from Products.Five. Retired 1.419 + Traversable and Viewable from the public API; use ZCML directives 1.420 + (five:traversable, five:defaultView) instead of mixins to make 1.421 + instances of classes work with Five. 1.422 + 1.423 +* classes that Five monkeypatches now have a __five_method__ 1.424 + attribute, making it easier for Five not to stomp on existing methods. 1.425 + 1.426 +* registered absolute_url view and IAbsoluteURL adapter for * 1.427 + 1.428 +* zope.app.traversing is registered by default, to make special 1.429 + namespaces available (eg: @@, ++resource++) 1.430 + 1.431 +* we now have resources (FileResource, ImageResource, 1.432 + PageTemplateResource) and directory resources. 1.433 + 1.434 +* Zope 3 'StandardMacros' now works with Five as well. 1.435 + 1.436 +* browser:page now correctly handles the allow_attributes and protects 1.437 + the named attributes on the view with the same permission used for 1.438 + the view. 1.439 + 1.440 +* zopeconf.py will try to find etc/zope.conf on INSTANCE_HOME. This 1.441 + requires Zope 2.7.2, as earlier Zope versions have a bug in this 1.442 + area which causes them to look in lib/python/Testing. 1.443 + 1.444 +* Exposed the Zope 3 event system to Five. A class can be made to send 1.445 + out event notifications using the five:sendEvents directive. Events can 1.446 + be subscribed to using the subscriber directive. 1.447 + 1.448 +* Change in findProducts so that non-filesystem products are skipped. 1.449 + 1.450 +Five 0.1 (2004-07-30) 1.451 +===================== 1.452 + 1.453 +Initial public release (mainly Martijn's work)
2.1 new file mode 100644 2.2 --- /dev/null 2.3 +++ b/COPYING.txt 2.4 @@ -0,0 +1,16 @@ 2.5 +Five is distributed under the provisions of the Zope Public License 2.6 +(ZPL) v2.1. See doc/ZopePublicLicense.txt for the license text. 2.7 + 2.8 +Copyright (C) 2005 Five Contributors. See CREDITS.txt for a list of 2.9 +Five contributors. 2.10 + 2.11 +Five contains source code derived from: 2.12 + 2.13 +- Zope 3, copyright (C) 2001-2005 by Zope Corporation. 2.14 + 2.15 +- metaclass.py is derived from PEAK, copyright (C) 1996-2004 by 2.16 + Phillip J. Eby and Tyler C. Sarna. PEAK may be used under the same 2.17 + terms as Zope. 2.18 + 2.19 +- TrustedExecutables. Dieter Mauer kindly allow licensing this under the 2.20 + ZPL 2.1.
3.1 new file mode 100644 3.2 --- /dev/null 3.3 +++ b/CREDITS.txt 3.4 @@ -0,0 +1,54 @@ 3.5 +Five contributors 3.6 +----------------- 3.7 + 3.8 +- Martijn Faassen (faassen@infrae.com) 3.9 + 3.10 +- Sidnei da Silva (sidnei@awkly.org) 3.11 + 3.12 +- Philipp von Weitershausen (philikon@philikon.de) 3.13 + 3.14 +- Lennart Regebro (regebro@nuxeo.com) 3.15 + 3.16 +- Tres Seaver (tseaver@palladion.com) 3.17 + 3.18 +- Jan-Wijbrand Kolman (jw@infrae.com) 3.19 + 3.20 +- Stefan Holek (ssh@epy.co.at) 3.21 + 3.22 +- Florent Guillaume (fg@nuxeo.com) 3.23 + 3.24 +- Godefroid Chapelle (gotcha@bubblenet.be) 3.25 + 3.26 +- Andy Adiwidjaja (mail@adiwidjaja.com) 3.27 + 3.28 +- Stuart Bishop (stuart@stuartbishop.net) 3.29 + 3.30 +- Simon Eisenmann (simon@struktur.de) 3.31 + 3.32 +- Dieter Maurer (dieter@handshake.de) 3.33 + 3.34 +- Yvo Schubbe (y.2005-@wcm-solutions.de) 3.35 + 3.36 +- Malcolm Cleaton (malcolm@jamkit.com) 3.37 + 3.38 +- Tarek Ziadé (tziade@nuxeo.com) 3.39 + 3.40 +- Whit Morriss (whit@longnow.org) 3.41 + 3.42 + 3.43 +Thank you 3.44 +--------- 3.45 + 3.46 +Infrae for the initial development and continuing support. 3.47 + 3.48 +Martijn Faassen would like to thank ETH Zurich for their support and 3.49 +encouragement during the initial development of Five. 3.50 + 3.51 +Nuxeo for significant contributions to making Five usable in the real 3.52 +world. 3.53 + 3.54 +Dieter Maurer for use of code from TrustedExecutables within Five 3.55 +under the ZPL. 3.56 + 3.57 +The Five developers would like to thank the Zope 3 developers, in 3.58 +particular Jim Fulton, for the mountain to stand on.
4.1 new file mode 100644 4.2 --- /dev/null 4.3 +++ b/INSTALL.txt 4.4 @@ -0,0 +1,39 @@ 4.5 +How to install Five 4.6 +=================== 4.7 + 4.8 +Requirements for Five 1.2 4.9 +------------------------- 4.10 + 4.11 +* Zope 2.8.1+ with Python 2.3.x. Zope 2.8.5+ is recommended as it 4.12 + contains a bugfix regarding Zope 3-style skins. 4.13 + 4.14 +Note that Five 1.0 is already part of Zope 2.8. This doesn't matter, 4.15 +though, you can still install Five 1.2 into your Zope 2.8 instances. 4.16 +It will override Zope's older version of Five. 4.17 + 4.18 + 4.19 +Compatability matrix 4.20 +-------------------- 4.21 + 4.22 +The following table shows which Five version can and should be used 4.23 +with which Zope 2 and Zope 3 versions. 4.24 + 4.25 +============ ======================= =========== ======== 4.26 +. Zope 2.7 Zope 2.8 Zope 2.9 4.27 +------------ ----------------------- ----------- -------- 4.28 +. Zope X3 3.0 (not incl.) Zope X3 3.0 Zope 3.2 4.29 +============ ======================= =========== ======== 4.30 +Five 1.0 X included 4.31 +Five 1.1[#]_ X X 4.32 +Five 1.2 X 4.33 +Five 1.3 included 4.34 +============ ======================= =========== ======== 4.35 + 4.36 +.. [#] This branch is no longer actively maintained. 4.37 + 4.38 + 4.39 +Running the tests 4.40 +----------------- 4.41 + 4.42 +For information on how to install the automatic Five tests, please see 4.43 +``tests/README.txt``.
5.1 new file mode 100644 5.2 --- /dev/null 5.3 +++ b/README.txt 5.4 @@ -0,0 +1,51 @@ 5.5 +Introduction 5.6 +------------ 5.7 + 5.8 +"It was the dawn of the third age of Zope. The Five project was a dream 5.9 +given form. Its goal: to use Zope 3 technologies in Zope 2.7 by 5.10 +creating a Zope 2 product where Zope 3 and Zope 2 could work out their 5.11 +differences peacefully." -- Babylon 5, creatively quoted 5.12 + 5.13 +"The Law of Fives states simply that: ALL THINGS HAPPEN IN FIVES, OR 5.14 +ARE DIVISIBLE BY OR ARE MULTIPLES OF FIVE, OR ARE SOMEHOW DIRECTLY OR 5.15 +INDIRECTLY RELATED TO FIVE. 5.16 + 5.17 +THE LAW OF FIVES IS NEVER WRONG." -- Principia Discordia 5.18 + 5.19 +What is Five? 5.20 +------------- 5.21 + 5.22 +The goal of five is to allow Zope 2 developers to use Zope 3 5.23 +technology right now, inside of Zope 2. Additionally, this allows a 5.24 +gradual evolution of Zope 2 code to Zope 3. 5.25 + 5.26 +Five already makes the following Zope 3 technologies available in Zope 5.27 +2: 5.28 + 5.29 +* Zope 3 interfaces 5.30 + 5.31 +* ZCML (Zope Configuration Markup Language) 5.32 + 5.33 +* Adapters 5.34 + 5.35 +* Zope 3 views, even for standard Zope objects 5.36 + 5.37 +* layers & skins 5.38 + 5.39 +* schema/forms machinery, including edit and add forms. 5.40 + 5.41 +* Zope 2 security declarations in ZCML instead of in Python code. 5.42 + 5.43 +Together with another product, CMFonFive, Five can integrate into CMF. 5.44 + 5.45 +For more information, see ``doc/features.txt``. 5.46 + 5.47 +How to install Five 5.48 +------------------- 5.49 + 5.50 +See ``INSTALL.txt``. 5.51 + 5.52 +How to use Five 5.53 +--------------- 5.54 + 5.55 +Please see ``doc/manual.txt``.
6.1 new file mode 100644 6.2 --- /dev/null 6.3 +++ b/__init__.py 6.4 @@ -0,0 +1,33 @@ 6.5 +############################################################################## 6.6 +# 6.7 +# Copyright (c) 2004, 2005 Zope Corporation and Contributors. 6.8 +# All Rights Reserved. 6.9 +# 6.10 +# This software is subject to the provisions of the Zope Public License, 6.11 +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 6.12 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 6.13 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 6.14 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 6.15 +# FOR A PARTICULAR PURPOSE. 6.16 +# 6.17 +############################################################################## 6.18 +"""Initialize the Five product 6.19 + 6.20 +$Id: __init__.py 12884 2005-05-30 13:10:41Z philikon $ 6.21 +""" 6.22 +import Acquisition 6.23 +from Globals import INSTANCE_HOME 6.24 + 6.25 +import monkey 6.26 +import zcml 6.27 + 6.28 +# trigger monkey patches 6.29 +monkey.monkeyPatch() 6.30 + 6.31 +# public API provided by Five 6.32 +# usage: from Products.Five import <something> 6.33 +from browser import BrowserView 6.34 +from skin.standardmacros import StandardMacros 6.35 + 6.36 +def initialize(context): 6.37 + zcml.load_site()
7.1 new file mode 100644 7.2 --- /dev/null 7.3 +++ b/bbb/AccessControl_interfaces.py 7.4 @@ -0,0 +1,235 @@ 7.5 +############################################################################## 7.6 +# 7.7 +# Copyright (c) 2004, 2005 Zope Corporation and Contributors. 7.8 +# All Rights Reserved. 7.9 +# 7.10 +# This software is subject to the provisions of the Zope Public License, 7.11 +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 7.12 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 7.13 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 7.14 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 7.15 +# FOR A PARTICULAR PURPOSE. 7.16 +# 7.17 +############################################################################## 7.18 +"""AccessControl z3 interfaces. 7.19 + 7.20 +$Id: AccessControl_interfaces.py 12884 2005-05-30 13:10:41Z philikon $ 7.21 +""" 7.22 +from zope.interface import Attribute 7.23 +from zope.interface import Interface 7.24 + 7.25 + 7.26 +# XXX: might contain non-API methods and outdated comments; 7.27 +# not synced with ZopeBook API Reference; 7.28 +# based on AccessControl.Owned.Owned 7.29 +class IOwned(Interface): 7.30 + 7.31 + manage_owner = Attribute("""Manage owner view""") 7.32 + 7.33 + def owner_info(): 7.34 + """Get ownership info for display 7.35 + """ 7.36 + 7.37 + def getOwner(info=0): 7.38 + """Get the owner 7.39 + 7.40 + If a true argument is provided, then only the owner path and id are 7.41 + returned. Otherwise, the owner object is returned. 7.42 + """ 7.43 + 7.44 + def getOwnerTuple(): 7.45 + """Return a tuple, (userdb_path, user_id) for the owner. 7.46 + 7.47 + o Ownership can be acquired, but only from the containment path. 7.48 + 7.49 + o If unowned, return None. 7.50 + """ 7.51 + 7.52 + def getWrappedOwner(): 7.53 + """Get the owner, modestly wrapped in the user folder. 7.54 + 7.55 + o If the object is not owned, return None. 7.56 + 7.57 + o If the owner's user database doesn't exist, return Nobody. 7.58 + 7.59 + o If the owner ID does not exist in the user database, return Nobody. 7.60 + """ 7.61 + 7.62 + def changeOwnership(user, recursive=0): 7.63 + """Change the ownership to the given user. If 'recursive' is 7.64 + true then also take ownership of all sub-objects, otherwise 7.65 + sub-objects retain their ownership information.""" 7.66 + 7.67 + def userCanTakeOwnership(): 7.68 + """ """ 7.69 + 7.70 + def manage_takeOwnership(REQUEST, RESPONSE, recursive=0): 7.71 + """ 7.72 + Take ownership (responsibility) for an object. If 'recursive' 7.73 + is true, then also take ownership of all sub-objects. 7.74 + """ 7.75 + 7.76 + def manage_changeOwnershipType(explicit=1, 7.77 + RESPONSE=None, REQUEST=None): 7.78 + """Change the type (implicit or explicit) of ownership. 7.79 + """ 7.80 + 7.81 + def _deleteOwnershipAfterAdd(): 7.82 + """ """ 7.83 + 7.84 + def manage_fixupOwnershipAfterAdd(): 7.85 + """ """ 7.86 + 7.87 + 7.88 +# XXX: might contain non-API methods and outdated comments; 7.89 +# not synced with ZopeBook API Reference; 7.90 +# based on AccessControl.PermissionMapping.RoleManager 7.91 +class IPermissionMappingSupport(Interface): 7.92 + 7.93 + def manage_getPermissionMapping(): 7.94 + """Return the permission mapping for the object 7.95 + 7.96 + This is a list of dictionaries with: 7.97 + 7.98 + permission_name -- The name of the native object permission 7.99 + 7.100 + class_permission -- The class permission the permission is 7.101 + mapped to. 7.102 + """ 7.103 + 7.104 + def manage_setPermissionMapping(permission_names=[], 7.105 + class_permissions=[], REQUEST=None): 7.106 + """Change the permission mapping 7.107 + """ 7.108 + 7.109 + 7.110 +# XXX: might contain non-API methods and outdated comments; 7.111 +# not synced with ZopeBook API Reference; 7.112 +# based on AccessControl.Role.RoleManager 7.113 +class IRoleManager(IPermissionMappingSupport): 7.114 + 7.115 + """An object that has configurable permissions""" 7.116 + 7.117 + permissionMappingPossibleValues = Attribute("""Acquired attribute""") 7.118 + 7.119 + def ac_inherited_permissions(all=0): 7.120 + """Get all permissions not defined in ourself that are inherited. 7.121 + 7.122 + This will be a sequence of tuples with a name as the first item and an 7.123 + empty tuple as the second. 7.124 + """ 7.125 + 7.126 + def permission_settings(permission=None): 7.127 + """Return user-role permission settings. If 'permission' 7.128 + is passed to the method then only the settings for 'permission' 7.129 + is returned. 7.130 + """ 7.131 + 7.132 + manage_roleForm = Attribute(""" """) 7.133 + 7.134 + def manage_role(role_to_manage, permissions=[], REQUEST=None): 7.135 + """Change the permissions given to the given role""" 7.136 + 7.137 + manage_acquiredForm = Attribute(""" """) 7.138 + 7.139 + def manage_acquiredPermissions(permissions=[], REQUEST=None): 7.140 + """Change the permissions that acquire""" 7.141 + 7.142 + manage_permissionForm = Attribute(""" """) 7.143 + 7.144 + def manage_permission(permission_to_manage, 7.145 + roles=[], acquire=0, REQUEST=None): 7.146 + """Change the settings for the given permission 7.147 + 7.148 + If optional arg acquire is true, then the roles for the permission 7.149 + are acquired, in addition to the ones specified, otherwise the 7.150 + permissions are restricted to only the designated roles.""" 7.151 + 7.152 + def manage_access(REQUEST, **kw): 7.153 + """Return an interface for making permissions settings""" 7.154 + 7.155 + def manage_changePermissions(REQUEST): 7.156 + """Change all permissions settings, called by management screen""" 7.157 + 7.158 + def permissionsOfRole(role): 7.159 + """used by management screen""" 7.160 + 7.161 + def rolesOfPermission(permission): 7.162 + """used by management screen""" 7.163 + 7.164 + def acquiredRolesAreUsedBy(permission): 7.165 + """used by management screen""" 7.166 + 7.167 + 7.168 + # Local roles support 7.169 + # ------------------- 7.170 + # 7.171 + # Local roles allow a user to be given extra roles in the context 7.172 + # of a particular object (and its children). When a user is given 7.173 + # extra roles in a particular object, an entry for that user is made 7.174 + # in the __ac_local_roles__ dict containing the extra roles. 7.175 + 7.176 + __ac_local_roles__ = Attribute(""" """) 7.177 + 7.178 + manage_listLocalRoles = Attribute(""" """) 7.179 + 7.180 + manage_editLocalRoles = Attribute(""" """) 7.181 + 7.182 + def has_local_roles(): 7.183 + """ """ 7.184 + 7.185 + def get_local_roles(): 7.186 + """ """ 7.187 + 7.188 + def users_with_local_role(role): 7.189 + """ """ 7.190 + 7.191 + def get_valid_userids(): 7.192 + """ """ 7.193 + 7.194 + def get_local_roles_for_userid(userid): 7.195 + """ """ 7.196 + 7.197 + def manage_addLocalRoles(userid, roles, REQUEST=None): 7.198 + """Set local roles for a user.""" 7.199 + 7.200 + def manage_setLocalRoles(userid, roles, REQUEST=None): 7.201 + """Set local roles for a user.""" 7.202 + 7.203 + def manage_delLocalRoles(userids, REQUEST=None): 7.204 + """Remove all local roles for a user.""" 7.205 + 7.206 + #------------------------------------------------------------ 7.207 + 7.208 + def access_debug_info(): 7.209 + """Return debug info""" 7.210 + 7.211 + def valid_roles(): 7.212 + """Return list of valid roles""" 7.213 + 7.214 + def validate_roles(roles): 7.215 + """Return true if all given roles are valid""" 7.216 + 7.217 + def userdefined_roles(): 7.218 + """Return list of user-defined roles""" 7.219 + 7.220 + def manage_defined_roles(submit=None, REQUEST=None): 7.221 + """Called by management screen.""" 7.222 + 7.223 + def _addRole(role, REQUEST=None): 7.224 + """ """ 7.225 + 7.226 + def _delRoles(roles, REQUEST=None): 7.227 + """ """ 7.228 + 7.229 + def _has_user_defined_role(role): 7.230 + """ """ 7.231 + 7.232 + def manage_editRoles(REQUEST, acl_type='A', acl_roles=[]): 7.233 + """ """ 7.234 + 7.235 + def _setRoles(acl_type, acl_roles): 7.236 + """ """ 7.237 + 7.238 + def possible_permissions(): 7.239 + """ """
8.1 new file mode 100644 8.2 --- /dev/null 8.3 +++ b/bbb/Acquisition_interfaces.py 8.4 @@ -0,0 +1,60 @@ 8.5 +############################################################################## 8.6 +# 8.7 +# Copyright (c) 2004, 2005 Zope Corporation and Contributors. 8.8 +# All Rights Reserved. 8.9 +# 8.10 +# This software is subject to the provisions of the Zope Public License, 8.11 +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 8.12 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 8.13 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 8.14 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 8.15 +# FOR A PARTICULAR PURPOSE. 8.16 +# 8.17 +############################################################################## 8.18 +"""Acquisition z3 interfaces. 8.19 + 8.20 +$Id: Acquisition_interfaces.py 12884 2005-05-30 13:10:41Z philikon $ 8.21 +""" 8.22 +from zope.interface import Attribute 8.23 +from zope.interface import Interface 8.24 + 8.25 + 8.26 +class IAcquirer(Interface): 8.27 + 8.28 + """Acquire attributes from containers.""" 8.29 + 8.30 + def __of__(context): 8.31 + """Get the object in a context.""" 8.32 + 8.33 + 8.34 +class IAcquisitionWrapper(Interface): 8.35 + 8.36 + """Wrapper object for acquisition.""" 8.37 + 8.38 + def aq_acquire(name, filter=None, extra=None, explicit=True, default=0, 8.39 + containment=0): 8.40 + """Get an attribute, acquiring it if necessary.""" 8.41 + 8.42 + def aq_inContextOf(obj, inner=1): 8.43 + """Test whether the object is currently in the context of the argument. 8.44 + """ 8.45 + 8.46 + aq_base = Attribute( 8.47 + """Get the object unwrapped.""" 8.48 + ) 8.49 + 8.50 + aq_parent = Attribute( 8.51 + """Get the parent of an object.""" 8.52 + ) 8.53 + 8.54 + aq_self = Attribute( 8.55 + """Get the object with the outermost wrapper removed.""" 8.56 + ) 8.57 + 8.58 + aq_inner = Attribute( 8.59 + """Get the object with all but the innermost wrapper removed.""" 8.60 + ) 8.61 + 8.62 + aq_chain = Attribute( 8.63 + """Get a list of objects in the acquisition environment.""" 8.64 + )
9.1 new file mode 100644 9.2 --- /dev/null 9.3 +++ b/bbb/App_interfaces.py 9.4 @@ -0,0 +1,78 @@ 9.5 +############################################################################## 9.6 +# 9.7 +# Copyright (c) 2004, 2005 Zope Corporation and Contributors. 9.8 +# All Rights Reserved. 9.9 +# 9.10 +# This software is subject to the provisions of the Zope Public License, 9.11 +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 9.12 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 9.13 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 9.14 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 9.15 +# FOR A PARTICULAR PURPOSE. 9.16 +# 9.17 +############################################################################## 9.18 +"""App z3 interfaces. 9.19 + 9.20 +$Id: App_interfaces.py 12884 2005-05-30 13:10:41Z philikon $ 9.21 +""" 9.22 +from zope.interface import Attribute 9.23 +from zope.interface import Interface 9.24 + 9.25 + 9.26 +# XXX: might contain non-API methods and outdated comments; 9.27 +# not synced with ZopeBook API Reference; 9.28 +# based on App.Management.Navigation 9.29 +class INavigation(Interface): 9.30 + 9.31 + """Basic navigation UI support""" 9.32 + 9.33 + manage = Attribute(""" """) 9.34 + manage_menu = Attribute(""" """) 9.35 + manage_top_frame = Attribute(""" """) 9.36 + manage_page_header = Attribute(""" """) 9.37 + manage_page_footer = Attribute(""" """) 9.38 + manage_form_title = Attribute("""Add Form""") 9.39 + zope_quick_start = Attribute(""" """) 9.40 + manage_copyright = Attribute(""" """) 9.41 + manage_zmi_prefs = Attribute(""" """) 9.42 + 9.43 + def manage_zmi_logout(REQUEST, RESPONSE): 9.44 + """Logout current user""" 9.45 + 9.46 +INavigation.setTaggedValue('manage_page_style.css', Attribute(""" """)) 9.47 + 9.48 + 9.49 +# XXX: might contain non-API methods and outdated comments; 9.50 +# not synced with ZopeBook API Reference; 9.51 +# based on App.PersistentExtra.PersistentUtil 9.52 +class IPersistentExtra(Interface): 9.53 + 9.54 + def bobobase_modification_time(): 9.55 + """ """ 9.56 + 9.57 + def locked_in_version(): 9.58 + """Was the object modified in any version? 9.59 + """ 9.60 + 9.61 + def modified_in_version(): 9.62 + """Was the object modified in this version? 9.63 + """ 9.64 + 9.65 + 9.66 +# XXX: might contain non-API methods and outdated comments; 9.67 +# not synced with ZopeBook API Reference; 9.68 +# based on App.Undo.UndoSupport 9.69 +class IUndoSupport(Interface): 9.70 + 9.71 + manage_UndoForm = Attribute("""Manage Undo form""") 9.72 + 9.73 + def get_request_var_or_attr(name, default): 9.74 + """ """ 9.75 + 9.76 + def undoable_transactions(first_transaction=None, 9.77 + last_transaction=None, 9.78 + PrincipiaUndoBatchSize=None): 9.79 + """ """ 9.80 + 9.81 + def manage_undo_transactions(transaction_info=(), REQUEST=None): 9.82 + """ """
10.1 new file mode 100644 10.2 --- /dev/null 10.3 +++ b/bbb/OFS_event.py 10.4 @@ -0,0 +1,61 @@ 10.5 +############################################################################## 10.6 +# 10.7 +# Copyright (c) 2005 Zope Corporation and Contributors. 10.8 +# All Rights Reserved. 10.9 +# 10.10 +# This software is subject to the provisions of the Zope Public License, 10.11 +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 10.12 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 10.13 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 10.14 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 10.15 +# FOR A PARTICULAR PURPOSE. 10.16 +# 10.17 +############################################################################## 10.18 +""" 10.19 +OFS event definitions. 10.20 + 10.21 +$Id: OFS_event.py 19416 2005-11-02 15:18:34Z efge $ 10.22 +""" 10.23 + 10.24 +from zope.interface import implements 10.25 +from zope.app.event.objectevent import ObjectEvent 10.26 +import OFS_interfaces 10.27 + 10.28 +class ObjectWillBeMovedEvent(ObjectEvent): 10.29 + """An object will be moved.""" 10.30 + implements(OFS_interfaces.IObjectWillBeMovedEvent) 10.31 + 10.32 + def __init__(self, object, oldParent, oldName, newParent, newName): 10.33 + ObjectEvent.__init__(self, object) 10.34 + self.oldParent = oldParent 10.35 + self.oldName = oldName 10.36 + self.newParent = newParent 10.37 + self.newName = newName 10.38 + 10.39 +class ObjectWillBeAddedEvent(ObjectWillBeMovedEvent): 10.40 + """An object will be added to a container.""" 10.41 + implements(OFS_interfaces.IObjectWillBeAddedEvent) 10.42 + 10.43 + def __init__(self, object, newParent=None, newName=None): 10.44 + #if newParent is None: 10.45 + # newParent = object.__parent__ 10.46 + #if newName is None: 10.47 + # newName = object.__name__ 10.48 + ObjectWillBeMovedEvent.__init__(self, object, None, None, 10.49 + newParent, newName) 10.50 + 10.51 +class ObjectWillBeRemovedEvent(ObjectWillBeMovedEvent): 10.52 + """An object will be removed from a container.""" 10.53 + implements(OFS_interfaces.IObjectWillBeRemovedEvent) 10.54 + 10.55 + def __init__(self, object, oldParent=None, oldName=None): 10.56 + #if oldParent is None: 10.57 + # oldParent = object.__parent__ 10.58 + #if oldName is None: 10.59 + # oldName = object.__name__ 10.60 + ObjectWillBeMovedEvent.__init__(self, object, oldParent, oldName, 10.61 + None, None) 10.62 + 10.63 +class ObjectClonedEvent(ObjectEvent): 10.64 + """An object has been cloned into a container.""" 10.65 + implements(OFS_interfaces.IObjectClonedEvent)
11.1 new file mode 100644 11.2 --- /dev/null 11.3 +++ b/bbb/OFS_interfaces.py 11.4 @@ -0,0 +1,771 @@ 11.5 +############################################################################## 11.6 +# 11.7 +# Copyright (c) 2004, 2005 Zope Corporation and Contributors. 11.8 +# All Rights Reserved. 11.9 +# 11.10 +# This software is subject to the provisions of the Zope Public License, 11.11 +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 11.12 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 11.13 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 11.14 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 11.15 +# FOR A PARTICULAR PURPOSE. 11.16 +# 11.17 +############################################################################## 11.18 +"""OFS z3 interfaces. 11.19 + 11.20 +$Id: OFS_interfaces.py 19416 2005-11-02 15:18:34Z efge $ 11.21 +""" 11.22 +from zope.interface import Attribute 11.23 +from zope.interface import Interface 11.24 +from zope.schema import Bool, BytesLine, Tuple 11.25 +from zope.app.traversing.interfaces import IContainmentRoot 11.26 + 11.27 +from AccessControl_interfaces import IOwned 11.28 +from AccessControl_interfaces import IRoleManager 11.29 +from Acquisition_interfaces import IAcquirer 11.30 +from App_interfaces import INavigation 11.31 +from App_interfaces import IUndoSupport 11.32 +from persistent.interfaces import IPersistent 11.33 +from webdav_interfaces import IDAVCollection 11.34 +from webdav_interfaces import IDAVResource 11.35 + 11.36 + 11.37 +# create IOrderedContainer 11.38 +from Products.Five.fiveconfigure import createZope2Bridge 11.39 +from OFS.IOrderSupport import IOrderedContainer as z2IOrderedContainer 11.40 +import OFS_interfaces 11.41 + 11.42 +createZope2Bridge(z2IOrderedContainer, OFS_interfaces, 'IOrderedContainer') 11.43 + 11.44 +del createZope2Bridge 11.45 +del z2IOrderedContainer 11.46 +del OFS_interfaces 11.47 + 11.48 + 11.49 +# XXX: might contain non-API methods and outdated comments; 11.50 +# not synced with ZopeBook API Reference; 11.51 +# based on OFS.CopySupport.CopySource 11.52 +class ICopySource(Interface): 11.53 + 11.54 + """Interface for objects which allow themselves to be copied.""" 11.55 + 11.56 + def _canCopy(op=0): 11.57 + """Called to make sure this object is copyable. The op var 11.58 + is 0 for a copy, 1 for a move.""" 11.59 + 11.60 + def _notifyOfCopyTo(container, op=0): 11.61 + """Overide this to be pickly about where you go! If you dont 11.62 + want to go there, raise an exception. The op variable is 11.63 + 0 for a copy, 1 for a move.""" 11.64 + 11.65 + def _getCopy(container): 11.66 + """ """ 11.67 + 11.68 + def _postCopy(container, op=0): 11.69 + """Called after the copy is finished to accomodate special cases. 11.70 + The op var is 0 for a copy, 1 for a move.""" 11.71 + 11.72 + def _setId(id): 11.73 + """Called to set the new id of a copied object.""" 11.74 + 11.75 + def cb_isCopyable(): 11.76 + """Is object copyable? Returns 0 or 1""" 11.77 + 11.78 + def cb_isMoveable(): 11.79 + """Is object moveable? Returns 0 or 1""" 11.80 + 11.81 + def cb_userHasCopyOrMovePermission(): 11.82 + """ """ 11.83 + 11.84 + 11.85 +# XXX: might contain non-API methods and outdated comments; 11.86 +# not synced with ZopeBook API Reference; 11.87 +# based on OFS.FTPInterface.FTPInterface 11.88 +class IFTPAccess(Interface): 11.89 + 11.90 + """Provide support for FTP access""" 11.91 + 11.92 + def manage_FTPstat(REQUEST): 11.93 + """Returns a stat-like tuple. (marshalled to a string) Used by 11.94 + FTP for directory listings, and MDTM and SIZE""" 11.95 + 11.96 + def manage_FTPlist(REQUEST): 11.97 + """Returns a directory listing consisting of a tuple of 11.98 + (id,stat) tuples, marshaled to a string. Note, the listing it 11.99 + should include '..' if there is a Folder above the current 11.100 + one. 11.101 + 11.102 + In the case of non-foldoid objects it should return a single 11.103 + tuple (id,stat) representing itself.""" 11.104 + 11.105 + 11.106 +# XXX: might contain non-API methods and outdated comments; 11.107 +# not synced with ZopeBook API Reference; 11.108 +# based on OFS.Traversable.Traversable 11.109 +class ITraversable(Interface): 11.110 + 11.111 + def absolute_url(relative=0): 11.112 + """Return the absolute URL of the object. 11.113 + 11.114 + This a canonical URL based on the object's physical 11.115 + containment path. It is affected by the virtual host 11.116 + configuration, if any, and can be used by external 11.117 + agents, such as a browser, to address the object. 11.118 + 11.119 + If the relative argument is provided, with a true value, then 11.120 + the value of virtual_url_path() is returned. 11.121 + 11.122 + Some Products incorrectly use '/'+absolute_url(1) as an 11.123 + absolute-path reference. This breaks in certain virtual 11.124 + hosting situations, and should be changed to use 11.125 + absolute_url_path() instead. 11.126 + """ 11.127 + 11.128 + def absolute_url_path(): 11.129 + """Return the path portion of the absolute URL of the object. 11.130 + 11.131 + This includes the leading slash, and can be used as an 11.132 + 'absolute-path reference' as defined in RFC 2396. 11.133 + """ 11.134 + 11.135 + def virtual_url_path(): 11.136 + """Return a URL for the object, relative to the site root. 11.137 + 11.138 + If a virtual host is configured, the URL is a path relative to 11.139 + the virtual host's root object. Otherwise, it is the physical 11.140 + path. In either case, the URL does not begin with a slash. 11.141 + """ 11.142 + 11.143 + def getPhysicalPath(): 11.144 + '''Returns a path (an immutable sequence of strings) 11.145 + that can be used to access this object again 11.146 + later, for example in a copy/paste operation. getPhysicalRoot() 11.147 + and getPhysicalPath() are designed to operate together. 11.148 + ''' 11.149 + 11.150 + def unrestrictedTraverse(path, default=None, restricted=0): 11.151 + """Lookup an object by path, 11.152 + 11.153 + path -- The path to the object. May be a sequence of strings or a slash 11.154 + separated string. If the path begins with an empty path element 11.155 + (i.e., an empty string or a slash) then the lookup is performed 11.156 + from the application root. Otherwise, the lookup is relative to 11.157 + self. Two dots (..) as a path element indicates an upward traversal 11.158 + to the acquisition parent. 11.159 + 11.160 + default -- If provided, this is the value returned if the path cannot 11.161 + be traversed for any reason (i.e., no object exists at that path or 11.162 + the object is inaccessible). 11.163 + 11.164 + restricted -- If false (default) then no security checking is performed. 11.165 + If true, then all of the objects along the path are validated with 11.166 + the security machinery. Usually invoked using restrictedTraverse(). 11.167 + """ 11.168 + 11.169 + def restrictedTraverse(path, default=None): 11.170 + """Trusted code traversal code, always enforces security""" 11.171 + 11.172 + 11.173 +# XXX: might contain non-API methods and outdated comments; 11.174 +# not synced with ZopeBook API Reference; 11.175 +# based on many classes 11.176 +class IZopeObject(Interface): 11.177 + 11.178 + isPrincipiaFolderish = Bool( 11.179 + title=u"Is a folderish object", 11.180 + description=u"Should be false for simple items", 11.181 + ) 11.182 + 11.183 + meta_type = BytesLine( 11.184 + title=u"Meta type", 11.185 + description=u"The object's Zope2 meta type", 11.186 + ) 11.187 + 11.188 + 11.189 +# XXX: might contain non-API methods and outdated comments; 11.190 +# not synced with ZopeBook API Reference; 11.191 +# based on OFS.SimpleItem.Item and App.Management.Tabs 11.192 +class IManageable(Interface): 11.193 + 11.194 + """Something that is manageable in the ZMI""" 11.195 + 11.196 + manage_tabs = Attribute("""Management tabs""") 11.197 + 11.198 + manage_options = Tuple( 11.199 + title=u"Manage options", 11.200 + ) 11.201 + 11.202 + def manage(URL1): 11.203 + """Show management screen""" 11.204 + 11.205 + def manage_afterAdd(item, container): 11.206 + """Gets called after being added to a container""" 11.207 + 11.208 + def manage_beforeDelete(item, container): 11.209 + """Gets called before being deleted""" 11.210 + 11.211 + def manage_afterClone(item): 11.212 + """Gets called after being cloned""" 11.213 + 11.214 + def manage_editedDialog(REQUEST, **args): 11.215 + """Show an 'edited' dialog""" 11.216 + 11.217 + def filtered_manage_options(REQUEST=None): 11.218 + """ """ 11.219 + 11.220 + def manage_workspace(REQUEST): 11.221 + """Dispatch to first interface in manage_options 11.222 + """ 11.223 + 11.224 + def tabs_path_default(REQUEST): 11.225 + """ """ 11.226 + 11.227 + def tabs_path_info(script, path): 11.228 + """ """ 11.229 + 11.230 + def class_manage_path(): 11.231 + """ """ 11.232 + 11.233 + 11.234 +# XXX: might contain non-API methods and outdated comments; 11.235 +# not synced with ZopeBook API Reference; 11.236 +# based on OFS.SimpleItem.Item 11.237 +class IItem(IZopeObject, IManageable, IFTPAccess, IDAVResource, 11.238 + ICopySource, ITraversable, IOwned, IUndoSupport): 11.239 + 11.240 + __name__ = BytesLine( 11.241 + title=u"Name" 11.242 + ) 11.243 + 11.244 + title = BytesLine( 11.245 + title=u"Title" 11.246 + ) 11.247 + 11.248 + icon = BytesLine( 11.249 + title=u"Icon", 11.250 + description=u"Name of icon, relative to SOFTWARE_URL", 11.251 + ) 11.252 + 11.253 + def getId(): 11.254 + """Return the id of the object as a string. 11.255 + 11.256 + This method should be used in preference to accessing an id 11.257 + attribute of an object directly. The getId method is public. 11.258 + """ 11.259 + 11.260 + def title_or_id(): 11.261 + """Returns the title if it is not blank and the id otherwise.""" 11.262 + 11.263 + def title_and_id(): 11.264 + """Returns the title if it is not blank and the id otherwise. If the 11.265 + title is not blank, then the id is included in parens.""" 11.266 + 11.267 + def raise_standardErrorMessage(client=None, REQUEST={}, 11.268 + error_type=None, error_value=None, tb=None, 11.269 + error_tb=None, error_message='', 11.270 + tagSearch=None, error_log_url=''): 11.271 + """Raise standard error message""" 11.272 + 11.273 + 11.274 +# XXX: based on OFS.SimpleItem.Item_w__name__ 11.275 +class IItemWithName(IItem): 11.276 + 11.277 + """Item with name""" 11.278 + 11.279 + 11.280 +# XXX: based on OFS.SimpleItem.SimpleItem 11.281 +class ISimpleItem(IItem, IPersistent, IAcquirer, IRoleManager): 11.282 + 11.283 + """Not-so-simple item""" 11.284 + 11.285 + 11.286 +# XXX: might contain non-API methods and outdated comments; 11.287 +# not synced with ZopeBook API Reference; 11.288 +# based on OFS.CopySupport.CopyContainer 11.289 +class ICopyContainer(Interface): 11.290 + 11.291 + """Interface for containerish objects which allow cut/copy/paste""" 11.292 + 11.293 + # The following three methods should be overridden to store sub-objects 11.294 + # as non-attributes. 11.295 + def _setOb(id, object): 11.296 + """ """ 11.297 + 11.298 + def _delOb(id): 11.299 + """ """ 11.300 + 11.301 + def _getOb(id, default=None): 11.302 + """ """ 11.303 + 11.304 + def manage_CopyContainerFirstItem(REQUEST): 11.305 + """ """ 11.306 + 11.307 + def manage_CopyContainerAllItems(REQUEST): 11.308 + """ """ 11.309 + 11.310 + def manage_cutObjects(ids=None, REQUEST=None): 11.311 + """Put a reference to the objects named in ids in the clip board""" 11.312 + 11.313 + def manage_copyObjects(ids=None, REQUEST=None, RESPONSE=None): 11.314 + """Put a reference to the objects named in ids in the clip board""" 11.315 + 11.316 + def _get_id(id): 11.317 + """Allow containers to override the generation of object copy id by 11.318 + attempting to call its _get_id method, if it exists.""" 11.319 + 11.320 + def manage_pasteObjects(cb_copy_data=None, REQUEST=None): 11.321 + """Paste previously copied objects into the current object. 11.322 + If calling manage_pasteObjects from python code, pass 11.323 + the result of a previous call to manage_cutObjects or 11.324 + manage_copyObjects as the first argument.""" 11.325 + 11.326 + manage_renameForm = Attribute("""Rename management view""") 11.327 + 11.328 + def manage_renameObjects(ids=[], new_ids=[], REQUEST=None): 11.329 + """Rename several sub-objects""" 11.330 + 11.331 + def manage_renameObject(id, new_id, REQUEST=None): 11.332 + """Rename a particular sub-object""" 11.333 + 11.334 + def manage_clone(ob, id, REQUEST=None): 11.335 + """Clone an object, creating a new object with the given id.""" 11.336 + 11.337 + def cb_dataValid(): 11.338 + """Return true if clipboard data seems valid.""" 11.339 + 11.340 + def cb_dataItems(): 11.341 + """List of objects in the clip board""" 11.342 + 11.343 + def _verifyObjectPaste(object, validate_src=1): 11.344 + """Verify whether the current user is allowed to paste the passed 11.345 + object into self. This is determined by checking to see if the 11.346 + user could create a new object of the same meta_type of the 11.347 + object passed in and checking that the user actually is 11.348 + allowed to access the passed in object in its existing 11.349 + context. 11.350 + 11.351 + Passing a false value for the validate_src argument will skip 11.352 + checking the passed in object in its existing context. This is 11.353 + mainly useful for situations where the passed in object has no 11.354 + existing context, such as checking an object during an import 11.355 + (the object will not yet have been connected to the 11.356 + acquisition hierarchy).""" 11.357 + 11.358 + 11.359 +# XXX: might contain non-API methods and outdated comments; 11.360 +# not synced with ZopeBook API Reference; 11.361 +# based on OFS.ObjectManager.ObjectManager 11.362 +class IObjectManager(IZopeObject, ICopyContainer, INavigation, IManageable, 11.363 + IAcquirer, IPersistent, IDAVCollection, ITraversable): 11.364 + 11.365 + """Generic object manager 11.366 + 11.367 + This interface provides core behavior for collections of heterogeneous 11.368 + objects. 11.369 + """ 11.370 + 11.371 + meta_types = Tuple( 11.372 + title=u"Meta types", 11.373 + description=u"Sub-object types that are specific to this object", 11.374 + ) 11.375 + 11.376 + isAnObjectManager = Bool( 11.377 + title=u"Is an object manager", 11.378 + ) 11.379 + 11.380 + manage_main = Attribute(""" """) 11.381 + manage_index_main = Attribute(""" """) 11.382 + manage_addProduct = Attribute(""" """) 11.383 + manage_importExportForm = Attribute(""" """) 11.384 + 11.385 + def all_meta_types(interfaces=None): 11.386 + """ """ 11.387 + 11.388 + def _subobject_permissions(): 11.389 + """ """ 11.390 + 11.391 + def filtered_meta_types(user=None): 11.392 + """Return a list of the types for which the user has adequate 11.393 + permission to add that type of object.""" 11.394 + 11.395 + def _setOb(id, object): 11.396 + """ """ 11.397 + 11.398 + def _delOb(id): 11.399 + """ """ 11.400 + 11.401 + def _getOb(id, default=None): 11.402 + """ """ 11.403 + 11.404 + def _setObject(id, object, roles=None, user=None, set_owner=1): 11.405 + """ """ 11.406 + 11.407 + def _delObject(id, dp=1): 11.408 + """ """ 11.409 + 11.410 + def objectIds(spec=None): 11.411 + """List the IDs of the subobjects of the current object. 11.412 + 11.413 + If 'spec' is specified, returns only objects whose meta_types match 11.414 + 'spec'. 11.415 + """ 11.416 + 11.417 + def objectValues(spec=None): 11.418 + """List the subobjects of the current object. 11.419 + 11.420 + If 'spec' is specified, returns only objects whose meta_types match 11.421 + 'spec'. 11.422 + """ 11.423 + 11.424 + def objectItems(spec=None): 11.425 + """List (ID, subobject) tuples for subobjects of the current object. 11.426 + 11.427 + If 'spec' is specified, returns only objects whose meta_types match 11.428 + 'spec'. 11.429 + """ 11.430 + 11.431 + def objectMap(): 11.432 + """Return a tuple of mappings containing subobject meta-data""" 11.433 + 11.434 + def superValues(t): 11.435 + """Return all of the objects of a given type located in this object 11.436 + and containing objects.""" 11.437 + 11.438 + def manage_delObjects(ids=[], REQUEST=None): 11.439 + """Delete a subordinate object 11.440 + 11.441 + The objects specified in 'ids' get deleted. 11.442 + """ 11.443 + 11.444 + def tpValues(): 11.445 + """Return a list of subobjects, used by tree tag.""" 11.446 + 11.447 + def manage_exportObject(id='', download=None, toxml=None, 11.448 + RESPONSE=None,REQUEST=None): 11.449 + """Exports an object to a file and returns that file.""" 11.450 + 11.451 + def manage_importObject(file, REQUEST=None, set_owner=1): 11.452 + """Import an object from a file""" 11.453 + 11.454 + def _importObjectFromFile(filepath, verify=1, set_owner=1): 11.455 + """ """ 11.456 + 11.457 + def __getitem__(key): 11.458 + """ """ 11.459 + 11.460 + 11.461 +# XXX: might contain non-API methods and outdated comments; 11.462 +# not synced with ZopeBook API Reference; 11.463 +# based on OFS.FindSupport.FindSupport 11.464 +class IFindSupport(Interface): 11.465 + 11.466 + """Find support for Zope Folders""" 11.467 + 11.468 + manage_findFrame = Attribute(""" """) 11.469 + manage_findForm = Attribute(""" """) 11.470 + manage_findAdv = Attribute(""" """) 11.471 + manage_findResult = Attribute(""" """) 11.472 + 11.473 + def ZopeFind(obj, obj_ids=None, obj_metatypes=None, 11.474 + obj_searchterm=None, obj_expr=None, 11.475 + obj_mtime=None, obj_mspec=None, 11.476 + obj_permission=None, obj_roles=None, 11.477 + search_sub=0, 11.478 + REQUEST=None, result=None, pre=''): 11.479 + """Zope Find interface""" 11.480 + 11.481 + PrincipiaFind = ZopeFind 11.482 + 11.483 + def ZopeFindAndApply(obj, obj_ids=None, obj_metatypes=None, 11.484 + obj_searchterm=None, obj_expr=None, 11.485 + obj_mtime=None, obj_mspec=None, 11.486 + obj_permission=None, obj_roles=None, 11.487 + search_sub=0, 11.488 + REQUEST=None, result=None, pre='', 11.489 + apply_func=None, apply_path=''): 11.490 + """Zope Find interface and apply""" 11.491 + 11.492 + 11.493 +# XXX: might contain non-API methods and outdated comments; 11.494 +# not synced with ZopeBook API Reference; 11.495 +# based on OFS.PropertyManager.PropertyManager 11.496 +class IPropertyManager(Interface): 11.497 + 11.498 + """ 11.499 + The PropertyManager mixin class provides an object with 11.500 + transparent property management. An object which wants to 11.501 + have properties should inherit from PropertyManager. 11.502 + 11.503 + An object may specify that it has one or more predefined 11.504 + properties, by specifying an _properties structure in its 11.505 + class:: 11.506 + 11.507 + _properties=({'id':'title', 'type': 'string', 'mode': 'w'}, 11.508 + {'id':'color', 'type': 'string', 'mode': 'w'}, 11.509 + ) 11.510 + 11.511 + The _properties structure is a sequence of dictionaries, where 11.512 + each dictionary represents a predefined property. Note that if a 11.513 + predefined property is defined in the _properties structure, you 11.514 + must provide an attribute with that name in your class or instance 11.515 + that contains the default value of the predefined property. 11.516 + 11.517 + Each entry in the _properties structure must have at least an 'id' 11.518 + and a 'type' key. The 'id' key contains the name of the property, 11.519 + and the 'type' key contains a string representing the object's type. 11.520 + The 'type' string must be one of the values: 'float', 'int', 'long', 11.521 + 'string', 'lines', 'text', 'date', 'tokens', 'selection', or 11.522 + 'multiple section'. 11.523 + 11.524 + For 'selection' and 'multiple selection' properties, there is an 11.525 + addition item in the property dictionay, 'select_variable' which 11.526 + provides the name of a property or method which returns a list of 11.527 + strings from which the selection(s) can be chosen. 11.528 + 11.529 + Each entry in the _properties structure may *optionally* provide a 11.530 + 'mode' key, which specifies the mutability of the property. The 'mode' 11.531 + string, if present, must contain 0 or more characters from the set 11.532 + 'w','d'. 11.533 + 11.534 + A 'w' present in the mode string indicates that the value of the 11.535 + property may be changed by the user. A 'd' indicates that the user 11.536 + can delete the property. An empty mode string indicates that the 11.537 + property and its value may be shown in property listings, but that 11.538 + it is read-only and may not be deleted. 11.539 + 11.540 + Entries in the _properties structure which do not have a 'mode' key 11.541 + are assumed to have the mode 'wd' (writeable and deleteable). 11.542 + 11.543 + To fully support property management, including the system-provided 11.544 + tabs and user interfaces for working with properties, an object which 11.545 + inherits from PropertyManager should include the following entry in 11.546 + its manage_options structure:: 11.547 + 11.548 + {'label':'Properties', 'action':'manage_propertiesForm',} 11.549 + 11.550 + to ensure that a 'Properties' tab is displayed in its management 11.551 + interface. Objects that inherit from PropertyManager should also 11.552 + include the following entry in its __ac_permissions__ structure:: 11.553 + 11.554 + ('Manage properties', ('manage_addProperty', 11.555 + 'manage_editProperties', 11.556 + 'manage_delProperties', 11.557 + 'manage_changeProperties',)), 11.558 + """ 11.559 + manage_propertiesForm = Attribute(""" """) 11.560 + manage_propertyTypeForm = Attribute(""" """) 11.561 + 11.562 + title = BytesLine( 11.563 + title=u"Title" 11.564 + ) 11.565 + 11.566 + _properties = Tuple( 11.567 + title=u"Properties", 11.568 + ) 11.569 + 11.570 + propertysheets = Attribute(""" """) 11.571 + 11.572 + def valid_property_id(id): 11.573 + """ """ 11.574 + 11.575 + def hasProperty(id): 11.576 + """Return true if object has a property 'id'""" 11.577 + 11.578 + def getProperty(id, d=None): 11.579 + """Get the property 'id', returning the optional second 11.580 + argument or None if no such property is found.""" 11.581 + 11.582 + def getPropertyType(id): 11.583 + """Get the type of property 'id', returning None if no 11.584 + such property exists""" 11.585 + 11.586 + def _wrapperCheck(object): 11.587 + """Raise an error if an object is wrapped.""" 11.588 + 11.589 + def _setPropValue(id, value): 11.590 + """ """ 11.591 + 11.592 + def _delPropValue(id): 11.593 + """ """ 11.594 + 11.595 + def _setProperty(id, value, type='string'): 11.596 + """Set property. 11.597 + 11.598 + For selection and multiple selection properties the value argument 11.599 + indicates the select variable of the property. 11.600 + """ 11.601 + 11.602 + def _updateProperty(id, value): 11.603 + """Update the value of an existing property. 11.604 + 11.605 + If value is a string, an attempt will be made to convert the value to 11.606 + the type of the existing property. 11.607 + """ 11.608 + 11.609 + def _delProperty(id): 11.610 + """ """ 11.611 + 11.612 + def propertyIds(): 11.613 + """Return a list of property ids """ 11.614 + 11.615 + def propertyValues(): 11.616 + """Return a list of actual property objects """ 11.617 + 11.618 + def propertyItems(): 11.619 + """Return a list of (id,property) tuples """ 11.620 + 11.621 + def _propertyMap(): 11.622 + """Return a tuple of mappings, giving meta-data for properties """ 11.623 + 11.624 + def propertyMap(): 11.625 + """ 11.626 + Return a tuple of mappings, giving meta-data for properties. 11.627 + Return copies of the real definitions for security. 11.628 + """ 11.629 + 11.630 + def propertyLabel(id): 11.631 + """Return a label for the given property id 11.632 + """ 11.633 + 11.634 + def propdict(): 11.635 + """ """ 11.636 + 11.637 + # Web interface 11.638 + 11.639 + def manage_addProperty(id, value, type, REQUEST=None): 11.640 + """Add a new property via the web. Sets a new property with 11.641 + the given id, type, and value.""" 11.642 + 11.643 + def manage_editProperties(REQUEST): 11.644 + """Edit object properties via the web. 11.645 + The purpose of this method is to change all property values, 11.646 + even those not listed in REQUEST; otherwise checkboxes that 11.647 + get turned off will be ignored. Use manage_changeProperties() 11.648 + instead for most situations. 11.649 + """ 11.650 + 11.651 + def manage_changeProperties(REQUEST=None, **kw): 11.652 + """Change existing object properties. 11.653 + 11.654 + Change object properties by passing either a mapping object 11.655 + of name:value pairs {'foo':6} or passing name=value parameters 11.656 + """ 11.657 + 11.658 + def manage_changePropertyTypes(old_ids, props, REQUEST=None): 11.659 + """Replace one set of properties with another 11.660 + 11.661 + Delete all properties that have ids in old_ids, then add a 11.662 + property for each item in props. Each item has a new_id, 11.663 + new_value, and new_type. The type of new_value should match 11.664 + new_type. 11.665 + """ 11.666 + 11.667 + def manage_delProperties(ids=None, REQUEST=None): 11.668 + """Delete one or more properties specified by 'ids'.""" 11.669 + 11.670 + 11.671 +# XXX: based on OFS.Folder.Folder 11.672 +class IFolder(IObjectManager, IPropertyManager, IRoleManager, 11.673 + IDAVCollection, IItem, IFindSupport): 11.674 + 11.675 + """Folders are basic container objects that provide a standard 11.676 + interface for object management. Folder objects also implement a 11.677 + management interface and can have arbitrary properties.""" 11.678 + 11.679 + 11.680 +# XXX: based on OFS.OrderedFolder.OrderedFolder 11.681 +class IOrderedFolder(IOrderedContainer, IFolder): 11.682 + 11.683 + """Ordered folder""" 11.684 + 11.685 + 11.686 +# XXX: might contain non-API methods and outdated comments; 11.687 +# not synced with ZopeBook API Reference; 11.688 +# based on OFS.Application.Application 11.689 +class IApplication(IFolder, IContainmentRoot): 11.690 + 11.691 + """Top-level system object""" 11.692 + 11.693 + isTopLevelPrincipiaApplicationObject = Bool( 11.694 + title=u"Is top level Principa application object", 11.695 + ) 11.696 + 11.697 + HelpSys = Attribute("Help system") 11.698 + 11.699 + p_ = Attribute(""" """) 11.700 + misc_ = Attribute("Misc.") 11.701 + 11.702 + def PrincipiaRedirect(destination, URL1): 11.703 + """Utility function to allow user-controlled redirects""" 11.704 + 11.705 + Redirect = ZopeRedirect = PrincipiaRedirect 11.706 + 11.707 + def __bobo_traverse__(REQUEST, name=None): 11.708 + """Bobo traverse""" 11.709 + 11.710 + def PrincipiaTime(*args): 11.711 + """Utility function to return current date/time""" 11.712 + 11.713 + ZopeTime = PrincipiaTime 11.714 + 11.715 + def ZopeAttributionButton(): 11.716 + """Returns an HTML fragment that displays the 'powered by zope' 11.717 + button along with a link to the Zope site.""" 11.718 + 11.719 + test_url = ZopeAttributionButton 11.720 + 11.721 + def absolute_url(relative=0): 11.722 + '''The absolute URL of the root object is BASE1 or "/".''' 11.723 + 11.724 + def absolute_url_path(): 11.725 + '''The absolute URL path of the root object is BASEPATH1 or "/".''' 11.726 + 11.727 + def virtual_url_path(): 11.728 + '''The virtual URL path of the root object is empty.''' 11.729 + 11.730 + def getPhysicalPath(): 11.731 + '''Returns a path that can be used to access this object again 11.732 + later, for example in a copy/paste operation. Designed to 11.733 + be used with getPhysicalRoot(). 11.734 + ''' 11.735 + 11.736 + def getPhysicalRoot(): 11.737 + """Returns self""" 11.738 + 11.739 + def fixupZClassDependencies(rebuild=0): 11.740 + """ """ 11.741 + 11.742 + def checkGlobalRegistry(): 11.743 + """Check the global (zclass) registry for problems, which can 11.744 + be caused by things like disk-based products being deleted. 11.745 + Return true if a problem is found""" 11.746 + 11.747 + 11.748 +################################################## 11.749 +# Event interfaces 11.750 + 11.751 +from zope.app.event.interfaces import IObjectEvent 11.752 + 11.753 +class IObjectWillBeMovedEvent(IObjectEvent): 11.754 + """An object will be moved.""" 11.755 + oldParent = Attribute("The old location parent for the object.") 11.756 + oldName = Attribute("The old location name for the object.") 11.757 + newParent = Attribute("The new location parent for the object.") 11.758 + newName = Attribute("The new location name for the object.") 11.759 + 11.760 +class IObjectWillBeAddedEvent(IObjectWillBeMovedEvent): 11.761 + """An object will be added to a container.""" 11.762 + 11.763 +class IObjectWillBeRemovedEvent(IObjectWillBeMovedEvent): 11.764 + """An object will be removed from a container""" 11.765 + 11.766 +class IObjectClonedEvent(IObjectEvent): 11.767 + """An object has been cloned (a la Zope 2). 11.768 + 11.769 + This is for Zope 2 compatibility, subscribers should really use 11.770 + IObjectCopiedEvent or IObjectAddedEvent, depending on their use 11.771 + cases. 11.772 + 11.773 + event.object is the copied object, already added to its container. 11.774 + Note that this event is dispatched to all sublocations. 11.775 + """
12.1 new file mode 100644 12.2 --- /dev/null 12.3 +++ b/bbb/OFS_subscribers.py 12.4 @@ -0,0 +1,162 @@ 12.5 +############################################################################## 12.6 +# 12.7 +# Copyright (c) 2005 Zope Corporation and Contributors. 12.8 +# All Rights Reserved. 12.9 +# 12.10 +# This software is subject to the provisions of the Zope Public License, 12.11 +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 12.12 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 12.13 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 12.14 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 12.15 +# FOR A PARTICULAR PURPOSE. 12.16 +# 12.17 +############################################################################## 12.18 +""" 12.19 +Five subscriber definitions. 12.20 + 12.21 +$Id: OFS_subscribers.py 19705 2005-11-10 14:00:27Z efge $ 12.22 +""" 12.23 + 12.24 +import warnings 12.25 +import sys 12.26 + 12.27 +from zLOG import LOG, ERROR 12.28 +from Acquisition import aq_base 12.29 +from App.config import getConfiguration 12.30 +from AccessControl import getSecurityManager 12.31 +from ZODB.POSException import ConflictError 12.32 +from OFS.ObjectManager import BeforeDeleteException 12.33 +from zope.app.container.contained import dispatchToSublocations 12.34 +from OFS_interfaces import IObjectManager 12.35 + 12.36 + 12.37 +deprecatedManageAddDeleteClasses = [] 12.38 + 12.39 + 12.40 +def compatibilityCall(method_name, *args): 12.41 + """Call a method if events have not been setup yet. 12.42 + 12.43 + This is the case for some unit tests that have not been converted to 12.44 + use the component architecture. 12.45 + """ 12.46 + if deprecatedManageAddDeleteClasses: 12.47 + # Events initialized, don't do compatibility call 12.48 + return 12.49 + if method_name == 'manage_afterAdd': 12.50 + callManageAfterAdd(*args) 12.51 + elif method_name == 'manage_beforeDelete': 12.52 + callManageBeforeDelete(*args) 12.53 + else: 12.54 + callManageAfterClone(*args) 12.55 + 12.56 +def maybeWarnDeprecated(ob, method_name): 12.57 + """Send a warning if a method is deprecated. 12.58 + """ 12.59 + if not deprecatedManageAddDeleteClasses: 12.60 + # Directives not fully loaded 12.61 + return 12.62 + for cls in deprecatedManageAddDeleteClasses: 12.63 + if isinstance(ob, cls): 12.64 + # Already deprecated through zcml 12.65 + return 12.66 + if getattr(getattr(ob, method_name), '__five_method__', False): 12.67 + # Method knows it's deprecated 12.68 + return 12.69 + class_ = ob.__class__ 12.70 + warnings.warn( 12.71 + "%s.%s.%s is deprecated and will be removed in Zope 2.11, " 12.72 + "you should use event subscribers instead, and meanwhile " 12.73 + "mark the class with <five:deprecatedManageAddDelete/>" 12.74 + % (class_.__module__, class_.__name__, method_name), 12.75 + DeprecationWarning) 12.76 + 12.77 + 12.78 +################################################## 12.79 +# Adapters and subscribers 12.80 + 12.81 +class ObjectManagerSublocations(object): 12.82 + """Get the sublocations for an ObjectManager. 12.83 + """ 12.84 + def __init__(self, container): 12.85 + self.container = container 12.86 + 12.87 + def sublocations(self): 12.88 + for ob in self.container.objectValues(): 12.89 + yield ob 12.90 + 12.91 +# The following subscribers should really be defined in ZCML 12.92 +# but we don't have enough control over subscriber ordering for 12.93 +# that to work exactly right. 12.94 +# (Sometimes IItem comes before IObjectManager, sometimes after, 12.95 +# depending on some of Zope's classes.) 12.96 +# This code can be simplified when Zope is completely rid of 12.97 +# manage_afterAdd & co, then IItem wouldn't be relevant anymore and we 12.98 +# could have a simple subscriber for IObjectManager that directly calls 12.99 +# dispatchToSublocations. 12.100 + 12.101 +def dispatchObjectWillBeMovedEvent(ob, event): 12.102 + """Multi-subscriber for IItem + IObjectWillBeMovedEvent. 12.103 + """ 12.104 + # First, dispatch to sublocations 12.105 + if IObjectManager.providedBy(ob): 12.106 + dispatchToSublocations(ob, event) 12.107 + # Next, do the manage_beforeDelete dance 12.108 + callManageBeforeDelete(ob, event.object, event.oldParent) 12.109 + 12.110 +def dispatchObjectMovedEvent(ob, event): 12.111 + """Multi-subscriber for IItem + IObjectMovedEvent. 12.112 + """ 12.113 + # First, do the manage_afterAdd dance 12.114 + callManageAfterAdd(ob, event.object, event.newParent) 12.115 + # Next, dispatch to sublocations 12.116 + if IObjectManager.providedBy(ob): 12.117 + dispatchToSublocations(ob, event) 12.118 + 12.119 +def dispatchObjectClonedEvent(ob, event): 12.120 + """Multi-subscriber for IItem + IObjectClonedEvent. 12.121 + """ 12.122 + # First, do the manage_afterClone dance 12.123 + callManageAfterClone(ob, event.object) 12.124 + # Next, dispatch to sublocations 12.125 + if IObjectManager.providedBy(ob): 12.126 + dispatchToSublocations(ob, event) 12.127 + 12.128 + 12.129 +def callManageAfterAdd(ob, item, container): 12.130 + """Compatibility subscriber for manage_afterAdd. 12.131 + """ 12.132 + if container is None: 12.133 + return 12.134 + if getattr(aq_base(ob), 'manage_afterAdd', None) is None: 12.135 + return 12.136 + maybeWarnDeprecated(ob, 'manage_afterAdd') 12.137 + ob.manage_afterAdd(item, container) 12.138 + 12.139 +def callManageBeforeDelete(ob, item, container): 12.140 + """Compatibility subscriber for manage_beforeDelete. 12.141 + """ 12.142 + if container is None: 12.143 + return 12.144 + if getattr(aq_base(ob), 'manage_beforeDelete', None) is None: 12.145 + return 12.146 + maybeWarnDeprecated(ob, 'manage_beforeDelete') 12.147 + try: 12.148 + ob.manage_beforeDelete(item, container) 12.149 + except BeforeDeleteException: 12.150 + raise 12.151 + except ConflictError: 12.152 + raise 12.153 + except: 12.154 + LOG('Zope', ERROR, '_delObject() threw', error=sys.exc_info()) 12.155 + # In debug mode when non-Manager, let exceptions propagate. 12.156 + if getConfiguration().debug_mode: 12.157 + if not getSecurityManager().getUser().has_role('Manager'): 12.158 + raise 12.159 + 12.160 +def callManageAfterClone(ob, item): 12.161 + """Compatibility subscriber for manage_afterClone. 12.162 + """ 12.163 + if getattr(aq_base(ob), 'manage_afterClone', None) is None: 12.164 + return 12.165 + maybeWarnDeprecated(ob, 'manage_afterClone') 12.166 + ob.manage_afterClone(item)
13.1 new file mode 100644 13.2 --- /dev/null 13.3 +++ b/bbb/__init__.py 13.4 @@ -0,0 +1,1 @@ 13.5 +# make this directory a package
14.1 new file mode 100644 14.2 --- /dev/null 14.3 +++ b/bbb/webdav_interfaces.py 14.4 @@ -0,0 +1,153 @@ 14.5 +############################################################################## 14.6 +# 14.7 +# Copyright (c) 2004, 2005 Zope Corporation and Contributors. 14.8 +# All Rights Reserved. 14.9 +# 14.10 +# This software is subject to the provisions of the Zope Public License, 14.11 +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 14.12 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 14.13 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 14.14 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 14.15 +# FOR A PARTICULAR PURPOSE. 14.16 +# 14.17 +############################################################################## 14.18 +"""webdav z3 interfaces. 14.19 + 14.20 +$Id: webdav_interfaces.py 12884 2005-05-30 13:10:41Z philikon $ 14.21 +""" 14.22 +from zope.interface import Interface 14.23 +from zope.schema import Bool, Tuple 14.24 + 14.25 + 14.26 +# create IWriteLock 14.27 +from Products.Five.fiveconfigure import createZope2Bridge 14.28 +from webdav.WriteLockInterface import WriteLockInterface 14.29 +import webdav_interfaces 14.30 + 14.31 +createZope2Bridge(WriteLockInterface, webdav_interfaces, 'IWriteLock') 14.32 + 14.33 +del createZope2Bridge 14.34 +del WriteLockInterface 14.35 +del webdav_interfaces 14.36 + 14.37 + 14.38 +# XXX: might contain non-API methods and outdated comments; 14.39 +# not synced with ZopeBook API Reference; 14.40 +# based on webdav.Resource.Resource 14.41 +class IDAVResource(IWriteLock): 14.42 + 14.43 + """Provide basic WebDAV support for non-collection objects.""" 14.44 + 14.45 + __dav_resource__ = Bool( 14.46 + title=u"Is DAV resource" 14.47 + ) 14.48 + 14.49 + __http_methods__ = Tuple( 14.50 + title=u"HTTP methods", 14.51 + description=u"Sequence of valid HTTP methods" 14.52 + ) 14.53 + 14.54 + def dav__init(request, response): 14.55 + """Init expected HTTP 1.1 / WebDAV headers which are not 14.56 + currently set by the base response object automagically. 14.57 + 14.58 + Also, we sniff for a ZServer response object, because we don't 14.59 + want to write duplicate headers (since ZS writes Date 14.60 + and Connection itself). 14.61 + """ 14.62 + 14.63 + def dav__validate(object, methodname, REQUEST): 14.64 + """ """ 14.65 + 14.66 + def dav__simpleifhandler(request, response, method='PUT', 14.67 + col=0, url=None, refresh=0): 14.68 + """ """ 14.69 + 14.70 + def HEAD(REQUEST, RESPONSE): 14.71 + """Retrieve resource information without a response body.""" 14.72 + 14.73 + def PUT(REQUEST, RESPONSE): 14.74 + """Replace the GET response entity of an existing resource. 14.75 + Because this is often object-dependent, objects which handle 14.76 + PUT should override the default PUT implementation with an 14.77 + object-specific implementation. By default, PUT requests 14.78 + fail with a 405 (Method Not Allowed).""" 14.79 + 14.80 + def OPTIONS(REQUEST, RESPONSE): 14.81 + """Retrieve communication options.""" 14.82 + 14.83 + def TRACE(REQUEST, RESPONSE): 14.84 + """Return the HTTP message received back to the client as the 14.85 + entity-body of a 200 (OK) response. This will often usually 14.86 + be intercepted by the web server in use. If not, the TRACE 14.87 + request will fail with a 405 (Method Not Allowed), since it 14.88 + is not often possible to reproduce the HTTP request verbatim 14.89 + from within the Zope environment.""" 14.90 + 14.91 + def DELETE(REQUEST, RESPONSE): 14.92 + """Delete a resource. For non-collection resources, DELETE may 14.93 + return either 200 or 204 (No Content) to indicate success.""" 14.94 + 14.95 + def PROPFIND(REQUEST, RESPONSE): 14.96 + """Retrieve properties defined on the resource.""" 14.97 + 14.98 + def PROPPATCH(REQUEST, RESPONSE): 14.99 + """Set and/or remove properties defined on the resource.""" 14.100 + 14.101 + def MKCOL(REQUEST, RESPONSE): 14.102 + """Create a new collection resource. If called on an existing 14.103 + resource, MKCOL must fail with 405 (Method Not Allowed).""" 14.104 + 14.105 + def COPY(REQUEST, RESPONSE): 14.106 + """Create a duplicate of the source resource whose state 14.107 + and behavior match that of the source resource as closely 14.108 + as possible. Though we may later try to make a copy appear 14.109 + seamless across namespaces (e.g. from Zope to Apache), COPY 14.110 + is currently only supported within the Zope namespace.""" 14.111 + 14.112 + def MOVE(REQUEST, RESPONSE): 14.113 + """Move a resource to a new location. Though we may later try to 14.114 + make a move appear seamless across namespaces (e.g. from Zope 14.115 + to Apache), MOVE is currently only supported within the Zope 14.116 + namespace.""" 14.117 + 14.118 + def LOCK(REQUEST, RESPONSE): 14.119 + """Lock a resource""" 14.120 + 14.121 + def UNLOCK(REQUEST, RESPONSE): 14.122 + """Remove an existing lock on a resource.""" 14.123 + 14.124 + def manage_DAVget(): 14.125 + """Gets the document source""" 14.126 + 14.127 + def listDAVObjects(): 14.128 + """ """ 14.129 + 14.130 + 14.131 +# XXX: might contain non-API methods and outdated comments; 14.132 +# not synced with ZopeBook API Reference; 14.133 +# based on webdav.Collection.Collection 14.134 +class IDAVCollection(IDAVResource): 14.135 + 14.136 + """The Collection class provides basic WebDAV support for 14.137 + collection objects. It provides default implementations 14.138 + for all supported WebDAV HTTP methods. The behaviors of some 14.139 + WebDAV HTTP methods for collections are slightly different 14.140 + than those for non-collection resources.""" 14.141 + 14.142 + __dav_collection__ = Bool( 14.143 + title=u"Is a DAV collection", 14.144 + description=u"Should be true", 14.145 + ) 14.146 + 14.147 + def PUT(REQUEST, RESPONSE): 14.148 + """The PUT method has no inherent meaning for collection 14.149 + resources, though collections are not specifically forbidden 14.150 + to handle PUT requests. The default response to a PUT request 14.151 + for collections is 405 (Method Not Allowed).""" 14.152 + 14.153 + def DELETE(REQUEST, RESPONSE): 14.154 + """Delete a collection resource. For collection resources, DELETE 14.155 + may return either 200 (OK) or 204 (No Content) to indicate total 14.156 + success, or may return 207 (Multistatus) to indicate partial 14.157 + success. Note that in Zope a DELETE currently never returns 207."""
15.1 new file mode 100644 15.2 --- /dev/null 15.3 +++ b/bridge.py 15.4 @@ -0,0 +1,89 @@ 15.5 +############################################################################## 15.6 +# 15.7 +# Copyright (c) 2004, 2005 Zope Corporation and Contributors. 15.8 +# All Rights Reserved. 15.9 +# 15.10 +# This software is subject to the provisions of the Zope Public License, 15.11 +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 15.12 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 15.13 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15.14 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 15.15 +# FOR A PARTICULAR PURPOSE. 15.16 +# 15.17 +############################################################################## 15.18 +""" Z2 -> Z3 bridge utilities. 15.19 + 15.20 +$Id: bridge.py 14504 2005-07-11 15:49:59Z yuppie $ 15.21 +""" 15.22 +from Interface._InterfaceClass import Interface as Z2_InterfaceClass 15.23 +from Interface import Interface as Z2_Interface 15.24 +from Interface import Attribute as Z2_Attribute 15.25 +from Interface.Method import Method as Z2_Method 15.26 + 15.27 +from zope.interface.interface import InterfaceClass as Z3_InterfaceClass 15.28 +from zope.interface.interface import Interface as Z3_Interface 15.29 +from zope.interface.interface import Attribute as Z3_Attribute 15.30 +from zope.interface.interface import Method as Z3_Method 15.31 + 15.32 +_bridges = {Z2_Interface: Z3_Interface} 15.33 + 15.34 +def fromZ2Interface(z2i): 15.35 + """ Return a Zope 3 interface corresponding to 'z2i'. 15.36 + 15.37 + o 'z2i' must be a Zope 2 interface. 15.38 + """ 15.39 + if not isinstance(z2i, Z2_InterfaceClass): 15.40 + raise ValueError, 'Not a Zope 2 interface!' 15.41 + 15.42 + if z2i in _bridges: 15.43 + return _bridges[z2i] 15.44 + 15.45 + name = z2i.getName() 15.46 + bases = [ fromZ2Interface(x) for x in z2i.getBases() ] 15.47 + attrs = {} 15.48 + 15.49 + for k, v in z2i.namesAndDescriptions(): 15.50 + if isinstance(v, Z2_Method): 15.51 + v = fromZ2Method(v) 15.52 + 15.53 + elif isinstance(v, Z2_Attribute): 15.54 + v = fromZ2Attribute(v) 15.55 + 15.56 + attrs[k] = v 15.57 + 15.58 + # XXX: Note that we pass the original interface's __module__; 15.59 + # we may live to regret that. 15.60 + z3i = Z3_InterfaceClass(name=name, 15.61 + bases=tuple(bases), 15.62 + attrs=attrs, 15.63 + __doc__=z2i.getDoc(), 15.64 + __module__=z2i.__module__) 15.65 + _bridges[z2i] = z3i 15.66 + return z3i 15.67 + 15.68 +def fromZ2Attribute(z2a): 15.69 + """ Return a Zope 3 interface attribute corresponding to 'z2a'. 15.70 + 15.71 + o 'z2a' must be a Zope 2 interface attribute. 15.72 + """ 15.73 + if not isinstance(z2a, Z2_Attribute): 15.74 + raise ValueError, 'Not a Zope 2 interface attribute!' 15.75 + 15.76 + return Z3_Attribute(z2a.getName(), z2a.getDoc()) 15.77 + 15.78 +def fromZ2Method(z2m): 15.79 + """ Return a Zope 3 interface method corresponding to 'z2m'. 15.80 + 15.81 + o 'z2m' must be a Zope 2 interface method. 15.82 + """ 15.83 + if not isinstance(z2m, Z2_Method): 15.84 + raise ValueError, 'Not a Zope 2 interface method!' 15.85 + 15.86 + z3m = Z3_Method(z2m.getName(), z2m.getDoc()) 15.87 + sig = z2m.getSignatureInfo() 15.88 + z3m.positional = sig['positional'] 15.89 + z3m.required = sig['required'] 15.90 + z3m.optional = sig['optional'] 15.91 + z3m.varargs = sig['varargs'] 15.92 + z3m.kwargs = sig['kwargs'] 15.93 + return z3m
16.1 new file mode 100644 16.2 --- /dev/null 16.3 +++ b/browser/ReuseUtils.py 16.4 @@ -0,0 +1,32 @@ 16.5 +############################################################################## 16.6 +# 16.7 +# Copyright (c) 2004, 2005 Zope Corporation and Contributors. 16.8 +# All Rights Reserved. 16.9 +# 16.10 +# This software is subject to the provisions of the Zope Public License, 16.11 +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 16.12 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 16.13 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16.14 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 16.15 +# FOR A PARTICULAR PURPOSE. 16.16 +# 16.17 +############################################################################## 16.18 +"""Utils to be reused 16.19 + 16.20 +$Id: ReuseUtils.py 12907 2005-05-31 06:26:15Z philikon $ 16.21 +""" 16.22 +from new import function 16.23 + 16.24 +def rebindFunction(f,rebindDir=None,**rebinds): 16.25 + '''return *f* with some globals rebound.''' 16.26 + d= {} 16.27 + if rebindDir : d.update(rebindDir) 16.28 + if rebinds: d.update(rebinds) 16.29 + if not d: return f 16.30 + f= getattr(f,'im_func',f) 16.31 + fd= f.func_globals.copy() 16.32 + fd.update(d) 16.33 + nf= function(f.func_code,fd,f.func_name,f.func_defaults or ()) 16.34 + nf.__doc__= f.__doc__ 16.35 + if f.__dict__ is not None: nf.__dict__= f.__dict__.copy() 16.36 + return nf
17.1 new file mode 100644 17.2 --- /dev/null 17.3 +++ b/browser/TrustedExpression.py 17.4 @@ -0,0 +1,108 @@ 17.5 +############################################################################## 17.6 +# 17.7 +# Copyright (c) 2004, 2005 Zope Corporation and Contributors. 17.8 +# All Rights Reserved. 17.9 +# 17.10 +# This software is subject to the provisions of the Zope Public License, 17.11 +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 17.12 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 17.13 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17.14 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 17.15 +# FOR A PARTICULAR PURPOSE. 17.16 +# 17.17 +############################################################################## 17.18 +"""Trusted expression 17.19 + 17.20 +$Id: TrustedExpression.py 18543 2005-10-14 13:37:26Z regebro $ 17.21 +""" 17.22 +from sys import modules 17.23 + 17.24 +from Products.PageTemplates.PythonExpr import PythonExpr 17.25 + 17.26 +from Products.PageTemplates.Expressions import \ 17.27 + SubPathExpr, PathExpr, \ 17.28 + StringExpr, \ 17.29 + getEngine, installHandlers,\ 17.30 + SecureModuleImporter 17.31 + 17.32 +from ReuseUtils import rebindFunction 17.33 + 17.34 +ModuleImporter = SecureModuleImporter 17.35 + 17.36 +def trustedTraverse(ob, path, ignored,): 17.37 + if not path: return self 17.38 + 17.39 + get = getattr 17.40 + has = hasattr 17.41 + N = None 17.42 + M = rebindFunction # artifical marker 17.43 + 17.44 + if isinstance(path, str): path = path.split('/') 17.45 + else: path=list(path) 17.46 + 17.47 + REQUEST={'TraversalRequestNameStack': path} 17.48 + path.reverse() 17.49 + pop=path.pop 17.50 + 17.51 + if len(path) > 1 and not path[0]: 17.52 + # Remove trailing slash 17.53 + path.pop(0) 17.54 + 17.55 + if not path[-1]: 17.56 + # If the path starts with an empty string, go to the root first. 17.57 + pop() 17.58 + self=ob.getPhysicalRoot() 17.59 + 17.60 + object = ob 17.61 + while path: 17.62 + name=pop() 17.63 + __traceback_info__ = path, name 17.64 + 17.65 + if name == '..': 17.66 + o=getattr(object, 'aq_parent', M) 17.67 + if o is not M: 17.68 + object=o 17.69 + continue 17.70 + 17.71 + t=get(object, '__bobo_traverse__', M) 17.72 + if t is not M: o=t(REQUEST, name) 17.73 + else: 17.74 + o = get(object, name, M) 17.75 + if o is M: 17.76 + try: o = object[name] 17.77 + except (AttributeError, TypeError): # better exception 17.78 + raise AttributeError(name) 17.79 + object = o 17.80 + 17.81 + return object 17.82 + 17.83 + 17.84 +class SubPathExpr(SubPathExpr): 17.85 + _eval = rebindFunction(SubPathExpr._eval.im_func, 17.86 + restrictedTraverse=trustedTraverse, 17.87 + ) 17.88 + 17.89 +class PathExpr(PathExpr): 17.90 + __init__ = rebindFunction(PathExpr.__init__.im_func, 17.91 + SubPathExpr=SubPathExpr, 17.92 + ) 17.93 + 17.94 +class StringExpr(StringExpr): 17.95 + __init__ = rebindFunction(StringExpr.__init__.im_func, 17.96 + PathExpr=PathExpr, 17.97 + ) 17.98 + 17.99 +installHandlers = rebindFunction(installHandlers, 17.100 + PathExpr=PathExpr, 17.101 + StringExpr=StringExpr, 17.102 + PythonExpr=PythonExpr, 17.103 + ) 17.104 + 17.105 +_engine=None 17.106 +getEngine = rebindFunction(getEngine, 17.107 + _engine=_engine, 17.108 + installHandlers=installHandlers 17.109 + ) 17.110 + 17.111 + 17.112 +
18.1 new file mode 100644 18.2 --- /dev/null 18.3 +++ b/browser/__init__.py 18.4 @@ -0,0 +1,32 @@ 18.5 +############################################################################## 18.6 +# 18.7 +# Copyright (c) 2004, 2005 Zope Corporation and Contributors. 18.8 +# All Rights Reserved. 18.9 +# 18.10 +# This software is subject to the provisions of the Zope Public License, 18.11 +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 18.12 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 18.13 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18.14 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 18.15 +# FOR A PARTICULAR PURPOSE. 18.16 +# 18.17 +############################################################################## 18.18 +"""Provide basic browser functionality 18.19 + 18.20 +$Id: __init__.py 12884 2005-05-30 13:10:41Z philikon $ 18.21 +""" 18.22 +import Acquisition 18.23 +from AccessControl import ClassSecurityInfo 18.24 +from Globals import InitializeClass 18.25 + 18.26 +class BrowserView(Acquisition.Explicit): 18.27 + security = ClassSecurityInfo() 18.28 + 18.29 + def __init__(self, context, request): 18.30 + self.context = context 18.31 + self.request = request 18.32 + 18.33 + # XXX do not create any methods on the subclass called index_html, 18.34 + # as this makes Zope 2 traverse into that first! 18.35 + 18.36 +InitializeClass(BrowserView)
19.1 new file mode 100644 19.2 --- /dev/null 19.3 +++ b/browser/absoluteurl.py 19.4 @@ -0,0 +1,84 @@ 19.5 +############################################################################## 19.6 +# 19.7 +# Copyright (c) 2004, 2005 Zope Corporation and Contributors. 19.8 +# All Rights Reserved. 19.9 +# 19.10 +# This software is subject to the provisions of the Zope Public License, 19.11 +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 19.12 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 19.13 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19.14 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 19.15 +# FOR A PARTICULAR PURPOSE. 19.16 +# 19.17 +############################################################################## 19.18 +"""Absolute URL 19.19 + 19.20 +$Id: absoluteurl.py 13254 2005-06-09 21:40:41Z philikon $ 19.21 +""" 19.22 +from Acquisition import aq_inner, aq_parent 19.23 +from OFS.interfaces import ITraversable 19.24 + 19.25 +from zope.interface import implements 19.26 +from zope.app import zapi 19.27 +from zope.app.traversing.browser.interfaces import IAbsoluteURL 19.28 + 19.29 +from Products.Five.browser import BrowserView 19.30 + 19.31 +class AbsoluteURL(BrowserView): 19.32 + """An adapter for Zope3-style absolute_url using Zope2 methods 19.33 + 19.34 + (original: zope.app.traversing.browser.absoluteurl) 19.35 + """ 19.36 + implements(IAbsoluteURL) 19.37 + 19.38 + def __init__(self, context, request): 19.39 + self.context, self.request = context, request 19.40 + 19.41 + def __str__(self): 19.42 + context = aq_inner(self.context) 19.43 + return context.absolute_url() 19.44 + 19.45 + __call__ = __str__ 19.46 + 19.47 + def breadcrumbs(self): 19.48 + context = aq_inner(self.context) 19.49 + container = aq_parent(context) 19.50 + request = self.request 19.51 + 19.52 + name = context.getId() 19.53 + 19.54 + if container is None or self._isVirtualHostRoot() \ 19.55 + or not ITraversable.providedBy(container): 19.56 + return ( 19.57 + {'name': name, 'url': context.absolute_url()},) 19.58 + 19.59 + view = zapi.getViewProviding(container, IAbsoluteURL, request) 19.60 + base = tuple(view.breadcrumbs()) 19.61 + base += ( 19.62 + {'name': name, 'url': ("%s/%s" % (base[-1]['url'], name))},) 19.63 + 19.64 + return base 19.65 + 19.66 + def _isVirtualHostRoot(self): 19.67 + virtualrootpath = self.request.get('VirtualRootPhysicalPath', None) 19.68 + if virtualrootpath is None: 19.69 + return False 19.70 + context = aq_inner(self.context) 19.71 + return context.restrictedTraverse(virtualrootpath) == context 19.72 + 19.73 +class SiteAbsoluteURL(AbsoluteURL): 19.74 + """An adapter for Zope3-style absolute_url using Zope2 methods 19.75 + 19.76 + This one is just used to stop breadcrumbs from crumbing up 19.77 + to the Zope root. 19.78 + 19.79 + (original: zope.app.traversing.browser.absoluteurl) 19.80 + """ 19.81 + 19.82 + def breadcrumbs(self): 19.83 + context = self.context 19.84 + request = self.request 19.85 + 19.86 + return ({'name': context.getId(), 19.87 + 'url': context.absolute_url() 19.88 + },)
20.1 new file mode 100644 20.2 --- /dev/null 20.3 +++ b/browser/adding.pt 20.4 @@ -0,0 +1,7 @@ 20.5 +<html metal:use-macro="context/@@standard_macros/page"> 20.6 +<body> 20.7 +<div metal:fill-slot="main"> 20.8 + <p>+ screen not yet supported by Five</p> 20.9 +</div> 20.10 +</body> 20.11 +</html>
21.1 new file mode 100644 21.2 --- /dev/null 21.3 +++ b/browser/adding.py 21.4 @@ -0,0 +1,261 @@ 21.5 +############################################################################## 21.6 +# 21.7 +# Copyright (c) 2002-2005 Zope Corporation and Contributors. 21.8 +# All Rights Reserved. 21.9 +# 21.10 +# This software is subject to the provisions of the Zope Public License, 21.11 +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 21.12 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 21.13 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21.14 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 21.15 +# FOR A PARTICULAR PURPOSE. 21.16 +# 21.17 +############################################################################## 21.18 +"""Adding View 21.19 + 21.20 +The Adding View is used to add new objects to a container. It is sort of a 21.21 +factory screen. 21.22 +""" 21.23 +__docformat__ = 'restructuredtext' 21.24 + 21.25 +from warnings import warn 21.26 +from zope.interface import implements 21.27 +from zope.publisher.interfaces import IPublishTraverse 21.28 +from zope.component.interfaces import IFactory 21.29 + 21.30 +from zope.app.exception.interfaces import UserError 21.31 +from zope.app.container.interfaces import IAdding, INameChooser 21.32 +from zope.app.container.interfaces import IContainerNamesContainer 21.33 +from zope.app.container.constraints import checkFactory, checkObject 21.34 + 21.35 +from zope.app import zapi 21.36 +from zope.app.event.objectevent import ObjectCreatedEvent 21.37 +from zope.event import notify 21.38 + 21.39 +from zExceptions import BadRequest 21.40 + 21.41 +from Products.Five import BrowserView 21.42 +from Products.Five.traversable import Traversable 21.43 +from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile 21.44 + 21.45 +from Acquisition import Implicit 21.46 +from OFS.SimpleItem import SimpleItem 21.47 + 21.48 +class BasicAdding(Implicit, BrowserView): 21.49 + implements(IAdding, IPublishTraverse) 21.50 + 21.51 + def add(self, content): 21.52 + """See zope.app.container.interfaces.IAdding 21.53 + """ 21.54 + container = self.context 21.55 + name = self.contentName 21.56 + chooser = INameChooser(container) 21.57 + 21.58 + # check precondition 21.59 + checkObject(container, name, content) 21.60 + 21.61 + if IContainerNamesContainer.providedBy(container): 21.62 + # The container picks it's own names. 21.63 + # We need to ask it to pick one. 21.64 + name = chooser.chooseName(self.contentName or '', content) 21.65 + else: 21.66 + request = self.request 21.67 + name = request.get('add_input_name', name) 21.68 + 21.69 + if name is None: 21.70 + name = chooser.chooseName(self.contentName or '', content) 21.71 + elif name == '': 21.72 + name = chooser.chooseName('', content) 21.73 + else: 21.74 + # Invoke the name chooser even when we have a 21.75 + # name. It'll do useful things with it like converting 21.76 + # the incoming unicode to an ASCII string. 21.77 + name = chooser.chooseName(name, container) 21.78 + 21.79 + content.id = name 21.80 + container._setObject(name, content) 21.81 + self.contentName = name # Set the added object Name 21.82 + return container._getOb(name) 21.83 + 21.84 + contentName = None # usually set by Adding traverser 21.85 + 21.86 + def nextURL(self): 21.87 + """See zope.app.container.interfaces.IAdding""" 21.88 + # XXX this is definitely not right for all or even most uses 21.89 + # of Five, but can be overridden by an AddView subclass, using 21.90 + # the class attribute of a zcml:addform directive 21.91 + return (str(zapi.getView(self.context, "absolute_url", self.request)) 21.92 + + '/manage_main') 21.93 + 21.94 + # set in BrowserView.__init__ 21.95 + request = None 21.96 + context = None 21.97 + 21.98 + def renderAddButton(self): 21.99 + warn("The renderAddButton method is deprecated, use nameAllowed", 21.100 + DeprecationWarning, 2) 21.101 + 21.102 + 21.103 + def publishTraverse(self, request, name): 21.104 + """See zope.app.container.interfaces.IAdding""" 21.105 + if '=' in name: 21.106 + view_name, content_name = name.split("=", 1) 21.107 + self.contentName = content_name 21.108 + 21.109 + if view_name.startswith('@@'): 21.110 + view_name = view_name[2:] 21.111 + return zapi.getView(self, view_name, request) 21.112 + 21.113 + if name.startswith('@@'): 21.114 + view_name = name[2:] 21.115 + else: 21.116 + view_name = name 21.117 + 21.118 + view = zapi.queryView(self, view_name, request) 21.119 + if view is not None: 21.120 + return view 21.121 + 21.122 + factory = zapi.queryUtility(IFactory, name) 21.123 + if factory is None: 21.124 + return super(BasicAdding, self).publishTraverse(request, name) 21.125 + 21.126 + return factory 21.127 + 21.128 + def action(self, type_name='', id=''): 21.129 + if not type_name: 21.130 + raise UserError("You must select the type of object to add.") 21.131 + 21.132 + if type_name.startswith('@@'): 21.133 + type_name = type_name[2:] 21.134 + 21.135 + if '/' in type_name: 21.136 + view_name = type_name.split('/', 1)[0] 21.137 + else: 21.138 + view_name = type_name 21.139 + 21.140 + if zapi.queryView(self, view_name, self.request) is not None: 21.141 + url = "%s/%s=%s" % ( 21.142 + zapi.getView(self, "absolute_url", self.request), 21.143 + type_name, id) 21.144 + self.request.response.redirect(url) 21.145 + return 21.146 + 21.147 + if not self.contentName: 21.148 + self.contentName = id 21.149 + 21.150 + factory = zapi.getUtility(IFactory, type_name) 21.151 + content = factory() 21.152 + 21.153 + notify(ObjectCreatedEvent(content)) 21.154 + self.add(content) 21.155 + self.request.response.redirect(self.nextURL()) 21.156 + 21.157 + def namesAccepted(self): 21.158 + return not IContainerNamesContainer.providedBy(self.context) 21.159 + 21.160 + def nameAllowed(self): 21.161 + """Return whether names can be input by the user.""" 21.162 + return not IContainerNamesContainer.providedBy(self.context) 21.163 + 21.164 + 21.165 +class Adding(BasicAdding): 21.166 + 21.167 + menu_id = None 21.168 + index = ZopeTwoPageTemplateFile("adding.pt") 21.169 + 21.170 + def addingInfo(self): 21.171 + """Return menu data. 21.172 + 21.173 + This is sorted by title. 21.174 + """ 21.175 + container = self.context 21.176 + menu_service = zapi.getService("BrowserMenu") 21.177 + result = [] 21.178 + for menu_id in (self.menu_id, 'zope.app.container.add'): 21.179 + if not menu_id: 21.180 + continue 21.181 + for item in menu_service.getMenu(menu_id, self, self.request): 21.182 + extra = item.get('extra') 21.183 + if extra: 21.184 + factory = extra.get('factory') 21.185 + if factory: 21.186 + factory = zapi.getUtility(IFactory, factory) 21.187 + if not checkFactory(container, None, factory): 21.188 + continue 21.189 + elif item['extra']['factory'] != item['action']: 21.190 + item['has_custom_add_view']=True 21.191 + result.append(item) 21.192 + 21.193 + result.sort(lambda a, b: cmp(a['title'], b['title'])) 21.194 + return result 21.195 + 21.196 + def isSingleMenuItem(self): 21.197 + "Return whether there is single menu item or not." 21.198 + return len(self.addingInfo()) == 1 21.199 + 21.200 + def hasCustomAddView(self): 21.201 + "This should be called only if there is `singleMenuItem` else return 0" 21.202 + if self.isSingleMenuItem(): 21.203 + menu_item = self.addingInfo()[0] 21.204 + if 'has_custom_add_view' in menu_item: 21.205 + return True 21.206 + return False 21.207 + 21.208 +class ContentAdding(Adding, Traversable, SimpleItem): 21.209 + 21.210 + menu_id = "add_content" 21.211 + 21.212 +class ObjectManagerNameChooser: 21.213 + """A name chooser for a Zope object manager. 21.214 + """ 21.215 + 21.216 + implements(INameChooser) 21.217 + 21.218 + def __init__(self, context): 21.219 + self.context = context 21.220 + 21.221 + def checkName(self, name, object): 21.222 + # ObjectManager can only deal with ASCII names. Specially 21.223 + # ObjectManager._checkId can only deal with strings. 21.224 + try: 21.225 + name = name.encode('ascii') 21.226 + except UnicodeDecodeError: 21.227 + raise UserError, "Id must contain only ASCII characters." 21.228 + 21.229 + try: 21.230 + self.context._checkId(name, allow_dup=False) 21.231 + except BadRequest, e: 21.232 + msg = ' '.join(e.args) or "Id is in use or invalid" 21.233 + raise UserError, msg 21.234 + 21.235 + def chooseName(self, name, object): 21.236 + if not name: 21.237 + name = object.__class__.__name__ 21.238 + else: 21.239 + try: 21.240 + name = name.encode('ascii') 21.241 + except UnicodeDecodeError: 21.242 + raise UserError, "Id must contain only ASCII characters." 21.243 + 21.244 + dot = name.rfind('.') 21.245 + if dot >= 0: 21.246 + suffix = name[dot:] 21.247 + name = name[:dot] 21.248 + else: 21.249 + suffix = '' 21.250 + 21.251 + n = name + suffix 21.252 + i = 0 21.253 + while True: 21.254 + i += 1 21.255 + try: 21.256 + self.context._getOb(n) 21.257 + except AttributeError: 21.258 + break 21.259 + n = name + '-' + str(i) + suffix 21.260 + 21.261 + # Make sure the name is valid. We may have started with 21.262 + # something bad. 21.263 + self.checkName(n, object) 21.264 + 21.265 + return n
22.1 new file mode 100644 22.2 --- /dev/null 22.3 +++ b/browser/configure.zcml 22.4 @@ -0,0 +1,76 @@ 22.5 +<configure xmlns="http://namespaces.zope.org/zope" 22.6 + xmlns:browser="http://namespaces.zope.org/browser"> 22.7 + 22.8 + <serviceType 22.9 + id="BrowserMenu" 22.10 + interface="zope.app.publisher.interfaces.browser.IBrowserMenuService" 22.11 + /> 22.12 + 22.13 + <service 22.14 + serviceType="BrowserMenu" 22.15 + permission="zope.Public" 22.16 + component="zope.app.publisher.browser.globalbrowsermenuservice.globalBrowserMenuService" 22.17 + /> 22.18 + 22.19 + <browser:defaultView name="index.html" /> 22.20 + 22.21 + <browser:page 22.22 + for="*" 22.23 + name="absolute_url" 22.24 + class=".absoluteurl.AbsoluteURL" 22.25 + permission="zope.Public" 22.26 + allowed_interface="zope.app.traversing.browser.interfaces.IAbsoluteURL" 22.27 + /> 22.28 + 22.29 + <view 22.30 + for="*" 22.31 + factory=".absoluteurl.AbsoluteURL" 22.32 + type="zope.publisher.interfaces.http.IHTTPRequest" 22.33 + permission="zope.Public" 22.34 + provides="zope.app.traversing.browser.interfaces.IAbsoluteURL" 22.35 + /> 22.36 + 22.37 + <browser:page 22.38 + for="zope.app.traversing.interfaces.IContainmentRoot" 22.39 + name="absolute_url" 22.40 + class=".absoluteurl.SiteAbsoluteURL" 22.41 + permission="zope.Public" 22.42 + allowed_interface="zope.app.traversing.browser.interfaces.IAbsoluteURL" 22.43 + /> 22.44 + 22.45 + <view 22.46 + for="zope.app.traversing.interfaces.IContainmentRoot" 22.47 + factory=".absoluteurl.SiteAbsoluteURL" 22.48 + type="zope.publisher.interfaces.http.IHTTPRequest" 22.49 + permission="zope.Public" 22.50 + provides="zope.app.traversing.browser.interfaces.IAbsoluteURL" 22.51 + /> 22.52 + 22.53 + <browser:view 22.54 + for="OFS.interfaces.IObjectManager" 22.55 + name="+" 22.56 + class=".adding.ContentAdding" 22.57 + permission="zope2.ViewManagementScreens" 22.58 + > 22.59 + 22.60 + <browser:page name="index.html" template="adding.pt" /> 22.61 + <browser:page name="action.html" attribute="action" /> 22.62 + 22.63 + </browser:view> 22.64 + 22.65 + <adapter 22.66 + for="OFS.interfaces.IObjectManager" 22.67 + factory=".adding.ObjectManagerNameChooser" 22.68 + provides="zope.app.container.interfaces.INameChooser" 22.69 + /> 22.70 + 22.71 + <!-- Menu access --> 22.72 + <browser:page 22.73 + for="*" 22.74 + name="view_get_menu" 22.75 + permission="zope.Public" 22.76 + class=".menu.MenuAccessView" 22.77 + allowed_interface="zope.app.publisher.interfaces.browser.IMenuAccessView" 22.78 + /> 22.79 + 22.80 +</configure>
23.1 new file mode 100644 23.2 --- /dev/null 23.3 +++ b/browser/menu.py 23.4 @@ -0,0 +1,29 @@ 23.5 +############################################################################## 23.6 +# 23.7 +# Copyright (c) 2005 Zope Corporation and Contributors. 23.8 +# All Rights Reserved. 23.9 +# 23.10 +# This software is subject to the provisions of the Zope Public License, 23.11 +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 23.12 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 23.13 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23.14 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 23.15 +# FOR A PARTICULAR PURPOSE. 23.16 +# 23.17 +############################################################################## 23.18 +"""Some menu code 23.19 + 23.20 +$Id: menu.py 14512 2005-07-11 18:40:51Z philikon $ 23.21 +""" 23.22 +from zope.interface import implements 23.23 +from zope.app import zapi 23.24 +from zope.app.publisher.interfaces.browser import IMenuAccessView 23.25 +from zope.app.servicenames import BrowserMenu 23.26 +from Products.Five import BrowserView 23.27 + 23.28 +class MenuAccessView(BrowserView): 23.29 + implements(IMenuAccessView) 23.30 + 23.31 + def __getitem__(self, menu_id): 23.32 + browser_menu_service = zapi.getService(BrowserMenu) 23.33 + return browser_menu_service.getMenu(menu_id, self.context, self.request)
24.1 new file mode 100644 24.2 --- /dev/null 24.3 +++ b/browser/meta.zcml 24.4 @@ -0,0 +1,107 @@ 24.5 +<configure 24.6 + xmlns="http://namespaces.zope.org/zope" 24.7 + xmlns:meta="http://namespaces.zope.org/meta"> 24.8 + 24.9 + <meta:directives namespace="http://namespaces.zope.org/browser"> 24.10 + 24.11 + <meta:directive 24.12 + name="layer" 24.13 + schema="zope.app.publisher.browser.metadirectives.ILayerDirective" 24.14 + handler="zope.app.publisher.browser.metaconfigure.layer" 24.15 + /> 24.16 + 24.17 + <meta:directive 24.18 + name="skin" 24.19 + schema="zope.app.publisher.browser.metadirectives.ISkinDirective" 24.20 + handler="zope.app.publisher.browser.metaconfigure.skin" 24.21 + /> 24.22 + 24.23 + <meta:directive 24.24 + name="defaultSkin" 24.25 + schema="zope.app.publisher.browser.metadirectives.IDefaultSkinDirective" 24.26 + handler="zope.app.publisher.browser.metaconfigure.defaultSkin" 24.27 + /> 24.28 + 24.29 + <meta:directive 24.30 + name="defaultView" 24.31 + schema="zope.app.publisher.browser.metadirectives.IDefaultViewDirective" 24.32 + handler=".metaconfigure.defaultView" 24.33 + /> 24.34 + 24.35 + <meta:directive 24.36 + name="page" 24.37 + schema="zope.app.publisher.browser.metadirectives.IPageDirective" 24.38 + handler=".metaconfigure.page" 24.39 + /> 24.40 + 24.41 + <meta:complexDirective 24.42 + name="pages" 24.43 + schema="zope.app.publisher.browser.metadirectives.IPagesDirective" 24.44 + handler=".metaconfigure.pages" 24.45 + > 24.46 + 24.47 + <meta:subdirective 24.48 + name="page" 24.49 + schema="zope.app.publisher.browser.metadirectives.IPagesPageSubdirective" 24.50 + /> 24.51 + 24.52 + </meta:complexDirective> 24.53 + 24.54 + <meta:directive 24.55 + name="resource" 24.56 + schema="zope.app.publisher.browser.metadirectives.IResourceDirective" 24.57 + handler=".metaconfigure.resource" 24.58 + /> 24.59 + 24.60 + <meta:directive 24.61 + name="resourceDirectory" 24.62 + schema="zope.app.publisher.browser.metadirectives.IResourceDirectoryDirective" 24.63 + handler=".metaconfigure.resourceDirectory" 24.64 + /> 24.65 + 24.66 + <meta:directive 24.67 + name="menu" 24.68 + schema="zope.app.publisher.browser.metadirectives.IMenuDirective" 24.69 + handler="zope.app.publisher.browser.globalbrowsermenuservice.menuDirective" 24.70 + /> 24.71 + 24.72 + <meta:directive 24.73 + name="menuItem" 24.74 + schema="zope.app.publisher.browser.metadirectives.IMenuItemDirective" 24.75 + handler="zope.app.publisher.browser.globalbrowsermenuservice.menuItemDirective" 24.76 + /> 24.77 + 24.78 + <meta:complexDirective 24.79 + name="menuItems" 24.80 + schema="zope.app.publisher.browser.metadirectives.IMenuItemsDirective" 24.81 + handler="zope.app.publisher.browser.globalbrowsermenuservice.menuItemsDirective" 24.82 + > 24.83 + 24.84 + <meta:subdirective 24.85 + name="menuItem" 24.86 + schema="zope.app.publisher.browser.metadirectives.IMenuItemSubdirective" 24.87 + /> 24.88 + 24.89 + </meta:complexDirective> 24.90 + 24.91 + <meta:complexDirective 24.92 + name="view" 24.93 + schema="zope.app.publisher.browser.metadirectives.IViewDirective" 24.94 + handler=".metaconfigure.view" 24.95 + > 24.96 + 24.97 + <meta:subdirective 24.98 + name="page" 24.99 + schema="zope.app.publisher.browser.metadirectives.IViewPageSubdirective" 24.100 + /> 24.101 + 24.102 + <meta:subdirective 24.103 + name="defaultPage" 24.104 + schema="zope.app.publisher.browser.metadirectives.IViewDefaultPageSubdirective" 24.105 + /> 24.106 + 24.107 + </meta:complexDirective> 24.108 + 24.109 + </meta:directives> 24.110 + 24.111 +</configure>
25.1 new file mode 100644 25.2 --- /dev/null 25.3 +++ b/browser/metaconfigure.py 25.4 @@ -0,0 +1,466 @@ 25.5 +############################################################################## 25.6 +# 25.7 +# Copyright (c) 2004, 2005 Zope Corporation and Contributors. 25.8 +# All Rights Reserved. 25.9 +# 25.10 +# This software is subject to the provisions of the Zope Public License, 25.11 +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 25.12 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 25.13 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 25.14 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 25.15 +# FOR A PARTICULAR PURPOSE. 25.16 +# 25.17 +############################################################################## 25.18 +"""Browser directives 25.19 + 25.20 +Directives to emulate the 'http://namespaces.zope.org/browser' 25.21 +namespace in ZCML known from zope.app. 25.22 + 25.23 +$Id: metaconfigure.py 18911 2005-10-25 09:36:24Z regebro $ 25.24 +""" 25.25 +import os 25.26 + 25.27 +from zope.interface import Interface 25.28 +from zope.component import getGlobalService, ComponentLookupError 25.29 +from zope.configuration.exceptions import ConfigurationError 25.30 +from zope.component.servicenames import Presentation 25.31 +from zope.publisher.interfaces.browser import IBrowserRequest 25.32 +from zope.app.publisher.browser.viewmeta import pages as zope_app_pages 25.33 +from zope.app.publisher.browser.viewmeta import view as zope_app_view 25.34 +from zope.app.publisher.browser.viewmeta import providesCallable 25.35 +from zope.app.publisher.browser.globalbrowsermenuservice import\ 25.36 + menuItemDirective 25.37 +from zope.app.component.metaconfigure import handler 25.38 +from zope.app.component.interface import provideInterface 25.39 +from zope.app.container.interfaces import IAdding 25.40 + 25.41 +from Products.Five.browser import BrowserView 25.42 +from Products.Five.browser.resource import FileResourceFactory, ImageResourceFactory 25.43 +from Products.Five.browser.resource import PageTemplateResourceFactory 25.44 +from Products.Five.browser.resource import DirectoryResourceFactory 25.45 +from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile 25.46 +from Products.Five.metaclass import makeClass 25.47 +from Products.Five.security import getSecurityInfo, protectClass, \ 25.48 + protectName, initializeClass 25.49 + 25.50 +import ExtensionClass 25.51 + 25.52 +def page(_context, name, permission, for_, 25.53 + layer='default', template=None, class_=None, 25.54 + allowed_interface=None, allowed_attributes=None, 25.55 + attribute='__call__', menu=None, title=None, 25.56 + ): 25.57 + 25.58 + _handle_menu(_context, menu, title, [for_], name, permission) 25.59 + 25.60 + if not (class_ or template): 25.61 + raise ConfigurationError("Must specify a class or template") 25.62 + if allowed_attributes is None: 25.63 + allowed_attributes = [] 25.64 + if allowed_interface is not None: 25.65 + for interface in allowed_interface: 25.66 + attrs = [n for n, d in interface.namesAndDescriptions(1)] 25.67 + allowed_attributes.extend(attrs) 25.68 + 25.69 + if attribute != '__call__': 25.70 + if template: 25.71 + raise ConfigurationError( 25.72 + "Attribute and template cannot be used together.") 25.73 + 25.74 + if not class_: 25.75 + raise ConfigurationError( 25.76 + "A class must be provided if attribute is used") 25.77 + 25.78 + if template: 25.79 + template = os.path.abspath(str(_context.path(template))) 25.80 + if not os.path.isfile(template): 25.81 + raise ConfigurationError("No such file", template) 25.82 + 25.83 + if class_: 25.84 + # new-style classes do not work with Five. As we want to import 25.85 + # packages from z3 directly, we ignore new-style classes for now. 25.86 + if type(class_) == type: 25.87 + return 25.88 + if attribute != '__call__': 25.89 + if not hasattr(class_, attribute): 25.90 + raise ConfigurationError( 25.91 + "The provided class doesn't have the specified attribute " 25.92 + ) 25.93 + cdict = getSecurityInfo(class_) 25.94 + if template: 25.95 + new_class = makeClassForTemplate(template, bases=(class_, ), 25.96 + cdict=cdict) 25.97 + elif attribute != "__call__": 25.98 + # we're supposed to make a page for an attribute (read: 25.99 + # method) and it's not __call__. We thus need to create a 25.100 + # new class using our mixin for attributes. 25.101 + cdict.update({'__page_attribute__': attribute}) 25.102 + new_class = makeClass(class_.__name__, 25.103 + (class_, ViewMixinForAttributes), 25.104 + cdict) 25.105 + 25.106 + # in case the attribute does not provide a docstring, 25.107 + # ZPublisher refuses to publish it. So, as a workaround, 25.108 + # we provide a stub docstring 25.109 + func = getattr(new_class, attribute) 25.110 + if not func.__doc__: 25.111 + # cannot test for MethodType/UnboundMethod here 25.112 + # because of ExtensionClass 25.113 + if hasattr(func, 'im_func'): 25.114 + # you can only set a docstring on functions, not 25.115 + # on method objects 25.116 + func = func.im_func 25.117 + func.__doc__ = "Stub docstring to make ZPublisher work" 25.118 + else: 25.119 + # we could use the class verbatim here, but we'll execute 25.120 + # some security declarations on it so we really shouldn't 25.121 + # modify the original. So, instead we make a new class 25.122 + # with just one base class -- the original 25.123 + new_class = makeClass(class_.__name__, (class_,), cdict) 25.124 + 25.125 + else: 25.126 + # template 25.127 + new_class = makeClassForTemplate(template) 25.128 + 25.129 + _handle_for(_context, for_) 25.130 + 25.131 + _context.action( 25.132 + discriminator = ('view', for_, name, IBrowserRequest, layer), 25.133 + callable = handler, 25.134 + args = (Presentation, 'provideAdapter', 25.135 + IBrowserRequest, new_class, name, [for_], Interface, layer, 25.136 + _context.info), 25.137 + ) 25.138 + _context.action( 25.139 + discriminator = ('five:protectClass', new_class), 25.140 + callable = protectClass, 25.141 + args = (new_class, permission) 25.142 + ) 25.143 + if allowed_attributes: 25.144 + for attr in allowed_attributes: 25.145 + _context.action( 25.146 + discriminator = ('five:protectName', new_class, attr), 25.147 + callable = protectName, 25.148 + args = (new_class, attr, permission) 25.149 + ) 25.150 + _context.action( 25.151 + discriminator = ('five:initialize:class', new_class), 25.152 + callable = initializeClass, 25.153 + args = (new_class,) 25.154 + ) 25.155 + 25.156 +class pages(zope_app_pages): 25.157 + 25.158 + def page(self, _context, name, attribute='__call__', template=None, 25.159 + menu=None, title=None): 25.160 + return page(_context, 25.161 + name=name, 25.162 + attribute=attribute, 25.163 + template=template, 25.164 + menu=menu, title=title, 25.165 + **(self.opts)) 25.166 + 25.167 +def defaultView(_context, name, for_=None): 25.168 + 25.169 + type = IBrowserRequest 25.170 + 25.171 + _context.action( 25.172 + discriminator = ('defaultViewName', for_, type, name), 25.173 + callable = handler, 25.174 + args = (Presentation, 25.175 + 'setDefaultViewName', for_, type, name), 25.176 + ) 25.177 + 25.178 + _handle_for(_context, for_) 25.179 + 25.180 +# view (named view with pages) 25.181 + 25.182 +class view(zope_app_view): 25.183 + 25.184 + def __call__(self): 25.185 + (_context, name, for_, permission, layer, class_, 25.186 + allowed_interface, allowed_attributes) = self.args 25.187 + 25.188 + required = {} 25.189 + 25.190 + cdict = {} 25.191 + pages = {} 25.192 + 25.193 + for pname, attribute, template in self.pages: 25.194 + try: 25.195 + s = getGlobalService(Presentation) 25.196 + except ComponentLookupError, err: 25.197 + pass 25.198 + 25.199 + if template: 25.200 + cdict[pname] = ZopeTwoPageTemplateFile(template) 25.201 + if attribute and attribute != name: 25.202 + cdict[attribute] = cdict[pname] 25.203 + else: 25.204 + if not hasattr(class_, attribute): 25.205 + raise ConfigurationError("Undefined attribute", 25.206 + attribute) 25.207 + 25.208 + attribute = attribute or pname 25.209 + required[pname] = permission 25.210 + 25.211 + pages[pname] = attribute 25.212 + 25.213 + # This should go away, but noone seems to remember what to do. :-( 25.214 + if hasattr(class_, 'publishTraverse'): 25.215 + 25.216 + def publishTraverse(self, request, name, 25.217 + pages=pages, getattr=getattr): 25.218 + 25.219 + if name in pages: 25.220 + return getattr(self, pages[name]) 25.221 + view = zapi.queryView(self, name, request) 25.222 + if view is not None: 25.223 + return view 25.224 + 25.225 + m = class_.publishTraverse.__get__(self) 25.226 + return m(request, name) 25.227 + 25.228 + else: 25.229 + def publishTraverse(self, request, name, 25.230 + pages=pages, getattr=getattr): 25.231 + 25.232 + if name in pages: 25.233 + return getattr(self, pages[name]) 25.234 + view = zapi.queryView(self, name, request) 25.235 + if view is not None: 25.236 + return view 25.237 + 25.238 + raise NotFoundError(self, name, request) 25.239 + 25.240 + cdict['publishTraverse'] = publishTraverse 25.241 + 25.242 + if not hasattr(class_, 'browserDefault'): 25.243 + if self.default or self.pages: 25.244 + default = self.default or self.pages[0][0] 25.245 + cdict['browserDefault'] = ( 25.246 + lambda self, request, default=default: 25.247 + (self, (default, )) 25.248 + ) 25.249 + elif providesCallable(class_): 25.250 + cdict['browserDefault'] = ( 25.251 + lambda self, request: (self, ()) 25.252 + ) 25.253 + 25.254 + if class_ is not None: 25.255 + bases = (class_, ViewMixinForTemplates) 25.256 + else: 25.257 + bases = (ViewMixinForTemplates) 25.258 + 25.259 + try: 25.260 + cname = str(name) 25.261 + except: 25.262 + cname = "GeneratedClass" 25.263 + 25.264 + newclass = makeClass(cname, bases, cdict) 25.265 + 25.266 + _handle_for(_context, for_) 25.267 + 25.268 + if self.provides is not None: 25.269 + _context.action( 25.270 + discriminator = None, 25.271 + callable = provideInterface, 25.272 + args = ('', self.provides) 25.273 + ) 25.274 + 25.275 + _context.action( 25.276 + discriminator = ('view', for_, name, IBrowserRequest, layer, 25.277 + self.provides), 25.278 + callable = handler, 25.279 + args = (Presentation, 'provideAdapter', 25.280 + IBrowserRequest, newclass, name, [for_], self.provides, 25.281 + layer, _context.info), 25.282 + ) 25.283 + 25.284 +def _handle_for(_context, for_): 25.285 + if for_ is not None: 25.286 + _context.action( 25.287 + discriminator = None, 25.288 + callable = provideInterface, 25.289 + args = ('', for_) 25.290 + ) 25.291 + 25.292 +def _handle_menu(_context, menu, title, for_, name, permission): 25.293 + if menu or title: 25.294 + if not (menu and title): 25.295 + raise ConfigurationError( 25.296 + "If either menu or title are specified, they must " 25.297 + "both be specified.") 25.298 + 25.299 + if len(for_) != 1: 25.300 + raise ConfigurationError( 25.301 + "Menus can be specified only for single-view, not for " 25.302 + "multi-views.") 25.303 + 25.304 + return menuItemDirective( 25.305 + _context, menu, for_[0], '@@' + str(name), title, 25.306 + permission=permission) 25.307 + 25.308 + return [] 25.309 + 25.310 +_factory_map = {'image':{'prefix':'ImageResource', 25.311 + 'count':0, 25.312 + 'factory':ImageResourceFactory}, 25.313 + 'file':{'prefix':'FileResource', 25.314 + 'count':0, 25.315 + 'factory':FileResourceFactory}, 25.316 + 'template':{'prefix':'PageTemplateResource', 25.317 + 'count':0, 25.318 + 'factory':PageTemplateResourceFactory} 25.319 + } 25.320 + 25.321 +def resource(_context, name, layer='default', permission='zope.Public', 25.322 + file=None, image=None, template=None): 25.323 + 25.324 + if ((file and image) or (file and template) or 25.325 + (image and template) or not (file or image or template)): 25.326 + raise ConfigurationError( 25.327 + "Must use exactly one of file or image or template" 25.328 + "attributes for resource directives" 25.329 + ) 25.330 + 25.331 + res = file or image or template 25.332 + res_type = ((file and 'file') or 25.333 + (image and 'image') or 25.334 + (template and 'template')) 25.335 + factory_info = _factory_map.get(res_type) 25.336 + factory_info['count'] += 1 25.337 + res_factory = factory_info['factory'] 25.338 + class_name = '%s%s' % (factory_info['prefix'], factory_info['count']) 25.339 + new_class = makeClass(class_name, (res_factory.resource,), {}) 25.340 + factory = res_factory(name, res, resource_factory=new_class) 25.341 + 25.342 + _context.action( 25.343 + discriminator = ('resource', name, IBrowserRequest, layer), 25.344 + callable = handler, 25.345 + args = (Presentation, 'provideResource', 25.346 + name, IBrowserRequest, factory, layer), 25.347 + ) 25.348 + _context.action( 25.349 + discriminator = ('five:protectClass', new_class), 25.350 + callable = protectClass, 25.351 + args = (new_class, permission) 25.352 + ) 25.353 + _context.action( 25.354 + discriminator = ('five:initialize:class', new_class), 25.355 + callable = initializeClass, 25.356 + args = (new_class,) 25.357 + ) 25.358 + 25.359 +_rd_map = {ImageResourceFactory:{'prefix':'DirContainedImageResource', 25.360 + 'count':0}, 25.361 + FileResourceFactory:{'prefix':'DirContainedFileResource', 25.362 + 'count':0}, 25.363 + PageTemplateResourceFactory:{'prefix':'DirContainedPTResource', 25.364 + 'count':0}, 25.365 + DirectoryResourceFactory:{'prefix':'DirectoryResource', 25.366 + 'count':0} 25.367 + } 25.368 + 25.369 +def resourceDirectory(_context, name, directory, layer='default', 25.370 + permission='zope.Public'): 25.371 + 25.372 + if not os.path.isdir(directory): 25.373 + raise ConfigurationError( 25.374 + "Directory %s does not exist" % directory 25.375 + ) 25.376 + 25.377 + resource = DirectoryResourceFactory.resource 25.378 + f_cache = {} 25.379 + resource_factories = dict(resource.resource_factories) 25.380 + resource_factories['default'] = resource.default_factory 25.381 + for ext, factory in resource_factories.items(): 25.382 + if f_cache.get(factory) is not None: 25.383 + continue 25.384 + factory_info = _rd_map.get(factory) 25.385 + factory_info['count'] += 1 25.386 + class_name = '%s%s' % (factory_info['prefix'], factory_info['count']) 25.387 + factory_name = '%s%s' % (factory.__name__, factory_info['count']) 25.388 + f_resource = makeClass(class_name, (factory.resource,), {}) 25.389 + f_cache[factory] = makeClass(factory_name, (factory,), 25.390 + {'resource':f_resource}) 25.391 + for ext, factory in resource_factories.items(): 25.392 + resource_factories[ext] = f_cache[factory] 25.393 + default_factory = resource_factories['default'] 25.394 + del resource_factories['default'] 25.395 + 25.396 + cdict = {'resource_factories':resource_factories, 25.397 + 'default_factory':default_factory} 25.398 + 25.399 + factory_info = _rd_map.get(DirectoryResourceFactory) 25.400 + factory_info['count'] += 1 25.401 + class_name = '%s%s' % (factory_info['prefix'], factory_info['count']) 25.402 + dir_factory = makeClass(class_name, (resource,), cdict) 25.403 + factory = DirectoryResourceFactory(name, directory, 25.404 + resource_factory=dir_factory) 25.405 + 25.406 + new_classes = [dir_factory, 25.407 + ] + [f.resource for f in f_cache.values()] 25.408 + 25.409 + _context.action( 25.410 + discriminator = ('resource', name, IBrowserRequest, layer), 25.411 + callable = handler, 25.412 + args = (Presentation, 'provideResource', 25.413 + name, IBrowserRequest, factory, layer), 25.414 + ) 25.415 + for new_class in new_classes: 25.416 + _context.action( 25.417 + discriminator = ('five:protectClass', new_class), 25.418 + callable = protectClass, 25.419 + args = (new_class, permission) 25.420 + ) 25.421 + _context.action( 25.422 + discriminator = ('five:initialize:class', new_class), 25.423 + callable = initializeClass, 25.424 + args = (new_class,) 25.425 + ) 25.426 + 25.427 +# 25.428 +# mixin classes / class factories 25.429 +# 25.430 + 25.431 +class ViewMixinForAttributes(BrowserView): 25.432 + 25.433 + # we have an attribute that we can simply tell ZPublisher to go to 25.434 + def __browser_default__(self, request): 25.435 + return self, (self.__page_attribute__,) 25.436 + 25.437 + # this is technically not needed because ZPublisher finds our 25.438 + # attribute through __browser_default__; but we also want to be 25.439 + # able to call pages from python modules, PythonScripts or ZPT 25.440 + def __call__(self, *args, **kw): 25.441 + attr = self.__page_attribute__ 25.442 + meth = getattr(self, attr) 25.443 + return meth(*args, **kw) 25.444 + 25.445 +class ViewMixinForTemplates(BrowserView): 25.446 + 25.447 + # short cut to get to macros more easily 25.448 + def __getitem__(self, name): 25.449 + if name == 'macros': 25.450 + return self.index.macros 25.451 + return self.index.macros[name] 25.452 + 25.453 + # make the template publishable 25.454 + def __call__(self, *args, **kw): 25.455 + return self.index(self, *args, **kw) 25.456 + 25.457 +def makeClassForTemplate(filename, globals=None, used_for=None, 25.458 + bases=(), cdict=None): 25.459 + # XXX needs to deal with security from the bases? 25.460 + if cdict is None: 25.461 + cdict = {} 25.462 + cdict.update({'index': ZopeTwoPageTemplateFile(filename, globals)}) 25.463 + bases += (ViewMixinForTemplates,) 25.464 + class_ = makeClass("SimpleViewClass from %s" % filename, bases, cdict) 25.465 + 25.466 + if used_for is not None: 25.467 + class_.__used_for__ = used_for 25.468 + 25.469 + return class_ 25.470 +
26.1 new file mode 100644 26.2 --- /dev/null 26.3 +++ b/browser/pagetemplatefile.py 26.4 @@ -0,0 +1,97 @@ 26.5 +############################################################################## 26.6 +# 26.7 +# Copyright (c) 2004, 2005 Zope Corporation and Contributors. 26.8 +# All Rights Reserved. 26.9 +# 26.10 +# This software is subject to the provisions of the Zope Public License, 26.11 +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 26.12 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 26.13 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 26.14 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 26.15 +# FOR A PARTICULAR PURPOSE. 26.16 +# 26.17 +############################################################################## 26.18 +"""A 'PageTemplateFile' without security restrictions. 26.19 + 26.20 +$Id: pagetemplatefile.py 15603 2005-08-04 09:56:10Z yuppie $ 26.21 +""" 26.22 +import os, sys 26.23 + 26.24 +from Globals import package_home 26.25 +from AccessControl import getSecurityManager 26.26 +from Shared.DC.Scripts.Bindings import Unauthorized, UnauthorizedBinding 26.27 +from Products.PageTemplates.PageTemplateFile import PageTemplateFile 26.28 + 26.29 +from zope.app.pagetemplate.viewpagetemplatefile import ViewMapper 26.30 +from zope.app.pagetemplate.viewpagetemplatefile import ViewPageTemplateFile 26.31 + 26.32 +from Products.Five.browser.ReuseUtils import rebindFunction 26.33 +from Products.Five.browser.TrustedExpression import getEngine, ModuleImporter 26.34 + 26.35 +class ZopeTwoPageTemplateFile(PageTemplateFile): 26.36 + """A strange hybrid between Zope 2 and Zope 3 page template. 26.37 + 26.38 + Uses Zope 2's engine, but with security disabled and with some 26.39 + initialization and API from Zope 3. 26.40 + """ 26.41 + 26.42 + def __init__(self, filename, _prefix=None, content_type=None): 26.43 + # XXX doesn't use content_type yet 26.44 + 26.45 + self.ZBindings_edit(self._default_bindings) 26.46 + 26.47 + path = self.get_path_from_prefix(_prefix) 26.48 + self.filename = os.path.join(path, filename) 26.49 + if not os.path.isfile(self.filename): 26.50 + raise ValueError("No such file", self.filename) 26.51 + 26.52 + basepath, ext = os.path.splitext(self.filename) 26.53 + self.__name__ = os.path.basename(basepath) 26.54 + 26.55 + def get_path_from_prefix(self, _prefix): 26.56 + if isinstance(_prefix, str): 26.57 + path = _prefix 26.58 + else: 26.59 + if _prefix is None: 26.60 + _prefix = sys._getframe(2).f_globals 26.61 + path = package_home(_prefix) 26.62 + return path 26.63 + 26.64 + _cook = rebindFunction(PageTemplateFile._cook, 26.65 + getEngine=getEngine) 26.66 + 26.67 + pt_render = rebindFunction(PageTemplateFile.pt_render, 26.68 + getEngine=getEngine) 26.69 + 26.70 + def _pt_getContext(self): 26.71 + try: 26.72 + root = self.getPhysicalRoot() 26.73 + view = self._getContext() 26.74 + except AttributeError: 26.75 + # self has no attribute getPhysicalRoot. This typically happens 26.76 + # when the template has no proper acquisition context. 26.77 + # That also means it has no view. /regebro 26.78 + root = self.context.getPhysicalRoot() 26.79 + view = None 26.80 + 26.81 + here = self.context.aq_inner 26.82 + 26.83 + request = getattr(root, 'REQUEST', None) 26.84 + c = {'template': self, 26.85 + 'here': here, 26.86 + 'context': here, 26.87 + 'container': here, 26.88 + 'nothing': None, 26.89 + 'options': {}, 26.90 + 'root': root, 26.91 + 'request': request, 26.92 + 'modules': ModuleImporter, 26.93 + } 26.94 + if view: 26.95 + c['view'] = view 26.96 + c['views'] = ViewMapper(here, request) 26.97 + 26.98 + return c 26.99 + 26.100 + pt_getContext = rebindFunction(_pt_getContext, 26.101 + SecureModuleImporter=ModuleImporter)
27.1 new file mode 100644 27.2 --- /dev/null 27.3 +++ b/browser/resource.py 27.4 @@ -0,0 +1,232 @@ 27.5 +############################################################################## 27.6 +# 27.7 +# Copyright (c) 2004, 2005 Zope Corporation and Contributors. 27.8 +# All Rights Reserved. 27.9 +# 27.10 +# This software is subject to the provisions of the Zope Public License, 27.11 +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 27.12 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 27.13 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 27.14 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 27.15 +# FOR A PARTICULAR PURPOSE. 27.16 +# 27.17 +############################################################################## 27.18 +"""Provide basic resource functionality 27.19 + 27.20 +$Id: resource.py 13268 2005-06-10 14:18:23Z philikon $ 27.21 +""" 27.22 +import os 27.23 +import urllib 27.24 + 27.25 +from Acquisition import Explicit 27.26 +from ComputedAttribute import ComputedAttribute 27.27 +from OFS.Traversable import Traversable as OFSTraversable 27.28 + 27.29 +from zope.exceptions import NotFoundError 27.30 +from zope.interface import implements 27.31 +from zope.component.interfaces import IResource 27.32 +from zope.component import getViewProviding 27.33 +from zope.publisher.interfaces.browser import IBrowserPublisher 27.34 +from zope.app.traversing.browser.interfaces import IAbsoluteURL 27.35 +from zope.app.datetimeutils import time as timeFromDateTimeString 27.36 +from zope.app.publisher.fileresource import File, Image 27.37 +from zope.app.publisher.pagetemplateresource import PageTemplate 27.38 +from zope.app.publisher.browser.resources import empty 27.39 + 27.40 +from Products.Five.browser import BrowserView 27.41 + 27.42 +_marker = [] 27.43 + 27.44 +class Resource(Explicit): 27.45 + """A publishable resource 27.46 + """ 27.47 + implements(IResource) 27.48 + 27.49 + def __init__(self, request): 27.50 + self.request = request 27.51 + 27.52 + def __call__(self): 27.53 + name = self.__name__ 27.54 + container = self.__parent__ 27.55 + 27.56 + url = str(getViewProviding(container, IAbsoluteURL, self.request)) 27.57 + url = urllib.unquote(url) 27.58 + if not isinstance(container, DirectoryResource): 27.59 + name = '++resource++%s' % name 27.60 + return "%s/%s" % (url, name) 27.61 + 27.62 +class PageTemplateResource(BrowserView, Resource): 27.63 + #implements(IBrowserPublisher) 27.64 + 27.65 + def __browser_default__(self, request): 27.66 + return self, ('render',) 27.67 + 27.68 + def render(self): 27.69 + """Rendered content""" 27.70 + pt = self.context 27.71 + return pt(self.request) 27.72 + 27.73 +class FileResource(BrowserView, Resource): 27.74 + """A publishable file-based resource""" 27.75 + #implements(IBrowserPublisher) 27.76 + 27.77 + def __browser_default__(self, request): 27.78 + return self, (request.REQUEST_METHOD,) 27.79 + 27.80 + def GET(self): 27.81 + """Default content""" 27.82 + file = self.context 27.83 + request = self.request 27.84 + response = request.response 27.85 + 27.86 + # HTTP If-Modified-Since header handling. This is duplicated 27.87 + # from OFS.Image.Image - it really should be consolidated 27.88 + # somewhere... 27.89 + header = request.environ.get('If-Modified-Since', None) 27.90 + if header is not None: 27.91 + header = header.split(';')[0] 27.92 + # Some proxies seem to send invalid date strings for this 27.93 + # header. If the date string is not valid, we ignore it 27.94 + # rather than raise an error to be generally consistent 27.95 + # with common servers such as Apache (which can usually 27.96 + # understand the screwy date string as a lucky side effect 27.97 + # of the way they parse it). 27.98 + try: mod_since=long(timeFromDateTimeString(header)) 27.99 + except: mod_since=None 27.100 + if mod_since is not None: 27.101 + if getattr(file, 'lmt', None): 27.102 + last_mod = long(file.lmt) 27.103 + else: 27.104 + last_mod = long(0) 27.105 + if last_mod > 0 and last_mod <= mod_since: 27.106 + response.setStatus(304) 27.107 + return '' 27.108 + 27.109 + response.setHeader('Content-Type', file.content_type) 27.110 + response.setHeader('Last-Modified', file.lmh) 27.111 + 27.112 + # Cache for one day 27.113 + response.setHeader('Cache-Control', 'public,max-age=86400') 27.114 + f = open(file.path, 'rb') 27.115 + data = f.read() 27.116 + f.close() 27.117 + 27.118 + return data 27.119 + 27.120 + def HEAD(self): 27.121 + file = self.context 27.122 + response = self.request.response 27.123 + response = self.request.response 27.124 + response.setHeader('Content-Type', file.content_type) 27.125 + response.setHeader('Last-Modified', file.lmh) 27.126 + # Cache for one day 27.127 + response.setHeader('Cache-Control', 'public,max-age=86400') 27.128 + return '' 27.129 + 27.130 +class ResourceFactory: 27.131 + 27.132 + factory = None 27.133 + resource = None 27.134 + 27.135 + def __init__(self, name, path, resource_factory=None): 27.136 + self.__name = name 27.137 + self.__rsrc = self.factory(path, name) 27.138 + if resource_factory is not None: 27.139 + self.resource = resource_factory 27.140 + 27.141 + def __call__(self, request): 27.142 + resource = self.resource(self.__rsrc, request) 27.143 + return resource 27.144 + 27.145 +def _PageTemplate(self, path, name): 27.146 + # PageTemplate doesn't take a name parameter, 27.147 + # which makes it different from FileResource. 27.148 + # This is probably an error. 27.149 + template = PageTemplate(path) 27.150 + template.__name__ = name 27.151 + return template 27.152 + 27.153 +class PageTemplateResourceFactory(ResourceFactory): 27.154 + """A factory for Page Template resources""" 27.155 + 27.156 + factory = _PageTemplate 27.157 + resource = PageTemplateResource 27.158 + 27.159 +class FileResourceFactory(ResourceFactory): 27.160 + """A factory for File resources""" 27.161 + 27.162 + factory = File 27.163 + resource = FileResource 27.164 + 27.165 +class ImageResourceFactory(ResourceFactory): 27.166 + """A factory for Image resources""" 27.167 + 27.168 + factory = Image 27.169 + resource = FileResource 27.170 + 27.171 + 27.172 +# we only need this class a context for DirectoryResource 27.173 +class Directory: 27.174 + 27.175 + def __init__(self, path, name): 27.176 + self.path = path 27.177 + self.__name__ = name 27.178 + 27.179 +class DirectoryResource(BrowserView, Resource, OFSTraversable): 27.180 + #implements(IBrowserPublisher) 27.181 + 27.182 + resource_factories = { 27.183 + 'gif': ImageResourceFactory, 27.184 + 'png': ImageResourceFactory, 27.185 + 'jpg': ImageResourceFactory, 27.186 + 'pt': PageTemplateResourceFactory, 27.187 + 'zpt': PageTemplateResourceFactory, 27.188 + 'html': PageTemplateResourceFactory, 27.189 + } 27.190 + 27.191 + default_factory = FileResourceFactory 27.192 + 27.193 + def __init__(self, context, request): 27.194 + BrowserView.__init__(self, context, request) 27.195 + # OFSTraversable.absolute_url() assumes self.REQUEST being 27.196 + # accessible: 27.197 + self.REQUEST = request 27.198 + 27.199 + def getId(self): 27.200 + name = self.__name__ 27.201 + if not name.startswith('++resource++'): 27.202 + name = '++resource++%s' % self.__name__ 27.203 + return name 27.204 + 27.205 + def __browser_default__(self, request): 27.206 + '''See interface IBrowserPublisher''' 27.207 + return empty, () 27.208 + 27.209 + def __getitem__(self, name): 27.210 + res = self.get(name, None) 27.211 + if res is None: 27.212 + raise KeyError, name 27.213 + return res 27.214 + 27.215 + def get(self, name, default=_marker): 27.216 + path = self.context.path 27.217 + filename = os.path.join(path, name) 27.218 + if not os.path.isfile(filename): 27.219 + if default is _marker: 27.220 + raise NotFoundError(name) 27.221 + return default 27.222 + ext = name.split('.')[-1] 27.223 + factory = self.resource_factories.get(ext, self.default_factory) 27.224 + resource = factory(name, filename)(self.request) 27.225 + resource.__name__ = name 27.226 + resource.__parent__ = self 27.227 + # XXX __of__ wrapping is usually done on traversal. 27.228 + # However, we don't want to subclass Traversable (or do we?) 27.229 + # The right thing should probably be a specific (and very simple) 27.230 + # traverser that does __getitem__ and __of__. 27.231 + return resource.__of__(self) 27.232 + 27.233 +class DirectoryResourceFactory(ResourceFactory): 27.234 + 27.235 + factory = Directory 27.236 + resource = DirectoryResource
28.1 new file mode 100644 28.2 --- /dev/null 28.3 +++ b/browser/tests/__init__.py 28.4 @@ -0,0 +1,1 @@ 28.5 +# import this
29.1 new file mode 100644 29.2 --- /dev/null 29.3 +++ b/browser/tests/adding.txt 29.4 @@ -0,0 +1,53 @@ 29.5 +============ 29.6 +Adding tests 29.7 +============ 29.8 + 29.9 +ObjectManagerNameChooser 29.10 +------------------------ 29.11 + 29.12 +First we need to import and setup some prerequisites: 29.13 + 29.14 + >>> from Products.Five.tests.testing import manage_addFiveTraversableFolder 29.15 + >>> from Products.Five.browser.adding import ObjectManagerNameChooser 29.16 + 29.17 + >>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid') 29.18 + >>> chooser = ObjectManagerNameChooser(self.folder) 29.19 + 29.20 +Now we can start. ``INameChooser`` defines a ``checkName()`` method 29.21 +that checks whether a given name is valid in the container or not. 29.22 +Under the hood, ``ObjectManagerNameChooser`` calls ``_checkId()`` of 29.23 +the object manager. Valid names/ids are those that aren't in use yet 29.24 +and don't contain invalid characters. 29.25 + 29.26 + >>> chooser.checkName('abc', object()) 29.27 + 29.28 + >>> chooser.checkName('testoid', object()) 29.29 + Traceback (most recent call last): 29.30 + ... 29.31 + UserError: The id "testoid" is invalid - it is already in use. 29.32 + 29.33 + >>> chooser.checkName('slash/slash', object()) 29.34 + Traceback (most recent call last): 29.35 + ... 29.36 + UserError: The id "slash/slash" contains characters illegal in URLs. 29.37 + 29.38 +``INameChooser`` also promises us a ``chooseName()`` method that 29.39 +chooses a name for us in case we don't have one or that chooses a 29.40 +different name in case the one we chose was invalid. 29.41 + 29.42 + >>> chooser.chooseName('', self.folder.testoid) 29.43 + 'FiveTraversableFolder' 29.44 + 29.45 + >>> chooser.chooseName('abc', self.folder.testoid) 29.46 + 'abc' 29.47 + 29.48 + >>> chooser.chooseName('testoid', self.folder.testoid) 29.49 + 'testoid-1' 29.50 + 29.51 +Of course, if we start out with something bad, it isn't going to 29.52 +become good automagically: 29.53 + 29.54 + >>> chooser.chooseName('slash/slash', object()) 29.55 + Traceback (most recent call last): 29.56 + ... 29.57 + UserError: The id "slash/slash" contains characters illegal in URLs.
30.1 new file mode 100644 30.2 --- /dev/null 30.3 +++ b/browser/tests/birdmacro.pt 30.4 @@ -0,0 +1,1 @@ 30.5 +<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>
31.1 new file mode 100644 31.2 --- /dev/null 31.3 +++ b/browser/tests/cockatiel.pt 31.4 @@ -0,0 +1,2 @@ 31.5 +<p>Have you ever seen a cockatiel?</p> 31.6 +<p tal:content="string:maybe">dunno</p>
32.1 new file mode 100644 32.2 --- /dev/null 32.3 +++ b/browser/tests/condor.pt 32.4 @@ -0,0 +1,3 @@ 32.5 +<p tal:content="context/mymethod">Alpha</p> 32.6 +<p tal:content="view/eagle">Beta</p> 32.7 +<div tal:replace="structure context/@@flamingo.html">Gamma</div> 32.8 \ No newline at end of file
33.1 new file mode 100644 33.2 --- /dev/null 33.3 +++ b/browser/tests/cps_test_localizer.pt 33.4 @@ -0,0 +1,7 @@ 33.5 +<html> 33.6 +<body> 33.7 +<!-- fivetest is a Zope 3 style i18n domain, default is a Localizer domain --> 33.8 +<p i18n:domain="fivetest" i18n:translate="">This is a message</p> 33.9 +<p i18n:domain="default" i18n:translate="">Object actions</p> 33.10 +</body> 33.11 +</html>
34.1 new file mode 100644 34.2 --- /dev/null 34.3 +++ b/browser/tests/cps_test_localizer.py 34.4 @@ -0,0 +1,61 @@ 34.5 +############################################################################## 34.6 +# 34.7 +# Copyright (c) 2005 Zope Corporation and Contributors. 34.8 +# All Rights Reserved. 34.9 +# 34.10 +# This software is subject to the provisions of the Zope Public License, 34.11 +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 34.12 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 34.13 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 34.14 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 34.15 +# FOR A PARTICULAR PURPOSE. 34.16 +# 34.17 +############################################################################## 34.18 +"""Test the Localizer language integration for CPS. This test 34.19 +requires a full blown CPS installation to run. It is therefore 34.20 +prefixed with ``cps_`` so it won't be picked up by the test runner. 34.21 + 34.22 +$Id: cps_test_localizer.py 14400 2005-07-07 17:55:08Z philikon $ 34.23 +""" 34.24 +import os, sys 34.25 +if __name__ == '__main__': 34.26 + execfile(os.path.join(sys.path[0], 'framework.py')) 34.27 + 34.28 +def test_suite(): 34.29 + from Testing.ZopeTestCase import installProduct, FunctionalDocFileSuite 34.30 + installProduct('Five') 34.31 + installProduct('BTreeFolder2') 34.32 + installProduct('CMFCalendar') 34.33 + installProduct('CMFCore') 34.34 + installProduct('CMFDefault') 34.35 + installProduct('CMFTopic') 34.36 + installProduct('DCWorkflow') 34.37 + installProduct('Localizer') 34.38 + installProduct('MailHost') 34.39 + installProduct('CPSCore') 34.40 + installProduct('CPSDefault') 34.41 + installProduct('CPSDirectory') 34.42 + installProduct('CPSUserFolder') 34.43 + installProduct('TranslationService') 34.44 + installProduct('SiteAccess') 34.45 + # these products should (and used to be) be optional, but they 34.46 + # aren't right now. 34.47 + installProduct('CPSForum') 34.48 + installProduct('CPSSubscriptions') 34.49 + installProduct('CPSNewsLetters') 34.50 + installProduct('CPSSchemas') 34.51 + installProduct('CPSDocument') 34.52 + installProduct('PortalTransforms') 34.53 + installProduct('Epoz') 34.54 + # optional products, but apparently still needed... 34.55 + installProduct('CPSRSS') 34.56 + installProduct('CPSChat') 34.57 + installProduct('CPSCalendar') 34.58 + installProduct('CPSCollector') 34.59 + installProduct('CPSMailBoxer') 34.60 + 34.61 + return FunctionalDocFileSuite('cps_test_localizer.txt', 34.62 + package='Products.Five.browser.tests') 34.63 + 34.64 +if __name__ == '__main__': 34.65 + framework()
35.1 new file mode 100644 35.2 --- /dev/null 35.3 +++ b/browser/tests/cps_test_localizer.txt 35.4 @@ -0,0 +1,115 @@ 35.5 +Localizer languages 35.6 +=================== 35.7 + 35.8 +Before we start, we need to set up a manager user to be able to create 35.9 +the portal: 35.10 + 35.11 + >>> uf = self.folder.acl_users 35.12 + >>> uf._doAddUser('manager', 'r00t', ['Manager'], []) 35.13 + 35.14 +We need to 1) configure the Zope 3 i18n message catalogs, 2) make the 35.15 +CPS portal traversable, 3) register the Localizer languagees adapter 35.16 +and 4) register our test page: 35.17 + 35.18 + >>> configure_zcml = """ 35.19 + ... <configure 35.20 + ... xmlns="http://namespaces.zope.org/zope" 35.21 + ... xmlns:browser="http://namespaces.zope.org/browser" 35.22 + ... xmlns:five="http://namespaces.zope.org/five" 35.23 + ... xmlns:i18n="http://namespaces.zope.org/i18n" 35.24 + ... > 35.25 + ... <configure package="Products.Five.tests"> 35.26 + ... <i18n:registerTranslations directory="locales" /> 35.27 + ... </configure> 35.28 + ... 35.29 + ... <five:traversable class="Products.CPSDefault.Portal.CPSDefaultSite" /> 35.30 + ... 35.31 + ... <adapter 35.32 + ... for="zope.publisher.interfaces.http.IHTTPRequest" 35.33 + ... provides="zope.i18n.interfaces.IUserPreferredLanguages" 35.34 + ... factory="Products.Five.i18n.LocalizerLanguages" 35.35 + ... /> 35.36 + ... 35.37 + ... <configure package="Products.Five.browser.tests"> 35.38 + ... <browser:page 35.39 + ... for="*" 35.40 + ... template="cps_test_localizer.pt" 35.41 + ... name="cps_test_localizer.html" 35.42 + ... permission="zope2.View" 35.43 + ... /> 35.44 + ... </configure> 35.45 + ... </configure> 35.46 + ... """ 35.47 + >>> from Products.Five import zcml 35.48 + >>> zcml.load_string(configure_zcml) 35.49 + 35.50 +Create a CPS portal. We print an additional line before creating it 35.51 +because PortalTransforms might print stuff to stdout and doctest 35.52 +doesn't allow us to ellide a first line. 35.53 + 35.54 + >>> print "Ignore lines after me"; print http(r""" 35.55 + ... POST /test_folder_1_/manage_addProduct/CPSDefault/manage_addCPSDefaultSite HTTP/1.1 35.56 + ... Authorization: Basic manager:r00t 35.57 + ... Content-Length: 269 35.58 + ... Content-Type: application/x-www-form-urlencoded 35.59 + ... 35.60 + ... 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""") 35.61 + Ignore lines after me 35.62 + ... 35.63 + HTTP/1.1 200 OK 35.64 + ... 35.65 + 35.66 +Now for some actual testing... Our test page is a simple ZPT 35.67 +translating two messages from different domains. The first domain is 35.68 +a Zope 3 style one, the second one comes from Localizer. 35.69 + 35.70 +Both systems should yield the same default language (English) when no 35.71 +language is specified whatsoever: 35.72 + 35.73 + >>> print http(r""" 35.74 + ... GET /test_folder_1_/cps/cps_test_localizer.html HTTP/1.1 35.75 + ... """) 35.76 + HTTP/1.1 200 OK 35.77 + ... 35.78 + <html> 35.79 + <body> 35.80 + <!-- fivetest is a Zope 3 style i18n domain, default is a Localizer domain --> 35.81 + <p>This is a message</p> 35.82 + <p>Object actions</p> 35.83 + </body> 35.84 + </html> 35.85 + 35.86 +Both systems should honour the HTTP ``Accept-Language`` header in the 35.87 +same way: 35.88 + 35.89 + >>> print http(r""" 35.90 + ... GET /test_folder_1_/cps/cps_test_localizer.html HTTP/1.1 35.91 + ... Accept-Language: de 35.92 + ... """) 35.93 + HTTP/1.1 200 OK 35.94 + ... 35.95 + <html> 35.96 + <body> 35.97 + <!-- fivetest is a Zope 3 style i18n domain, default is a Localizer domain --> 35.98 + <p>Dies ist eine Nachricht</p> 35.99 + <p>Objekt Aktionen</p> 35.100 + </body> 35.101 + </html> 35.102 + 35.103 +Both systems should also honour Localizer-specific ways of determining 35.104 +the language, for example the ``LOCALIZER_LANGUAGE`` cookie: 35.105 + 35.106 + >>> print http(r""" 35.107 + ... GET /test_folder_1_/cps/cps_test_localizer.html HTTP/1.1 35.108 + ... Accept-Language: de 35.109 + ... Cookie: LOCALIZER_LANGUAGE=en 35.110 + ... """) 35.111 + HTTP/1.1 200 OK 35.112 + ... 35.113 + <html> 35.114 + <body> 35.115 + <!-- fivetest is a Zope 3 style i18n domain, default is a Localizer domain --> 35.116 + <p>This is a message</p> 35.117 + <p>Object actions</p> 35.118 + </body> 35.119 + </html>
36.1 new file mode 100644 36.2 --- /dev/null 36.3 +++ b/browser/tests/defaultview.zcml 36.4 @@ -0,0 +1,38 @@ 36.5 +<configure xmlns="http://namespaces.zope.org/zope" 36.6 + xmlns:browser="http://namespaces.zope.org/browser" 36.7 + xmlns:five="http://namespaces.zope.org/five"> 36.8 + 36.9 + <five:defaultViewable 36.10 + class="Products.Five.tests.testing.simplecontent.SimpleContent" /> 36.11 + 36.12 + <browser:defaultView 36.13 + for="Products.Five.tests.testing.simplecontent.ISimpleContent" 36.14 + name="eagledefaultview.txt" 36.15 + /> 36.16 + 36.17 + <browser:page 36.18 + for="Products.Five.tests.testing.simplecontent.ISimpleContent" 36.19 + name="eagledefaultview.txt" 36.20 + class=".pages.SimpleView" 36.21 + attribute="eagle" 36.22 + permission="zope2.Public" 36.23 + /> 36.24 + 36.25 + <!-- this tests whether five:defaultViewable can be called on a class that 36.26 + already provides __call__, such as our CallableSimpleContent --> 36.27 + 36.28 + <five:defaultViewable 36.29 + class="Products.Five.tests.testing.simplecontent.CallableSimpleContent" /> 36.30 + 36.31 + <!-- this tests whether five:defaultViewable can be called on a class that 36.32 + already provides index_html, such as our IndexSimpleContent --> 36.33 + 36.34 + <five:defaultViewable 36.35 + class="Products.Five.tests.testing.simplecontent.IndexSimpleContent" /> 36.36 + 36.37 + <browser:defaultView 36.38 + for="Products.Five.tests.testing.simplecontent.IIndexSimpleContent" 36.39 + name="index_html" 36.40 + /> 36.41 + 36.42 +</configure>
37.1 new file mode 100644 37.2 --- /dev/null 37.3 +++ b/browser/tests/falcon.pt 37.4 @@ -0,0 +1,1 @@ 37.5 +<p>The falcon has taken flight</p> 37.6 \ No newline at end of file
38.1 new file mode 100644 38.2 --- /dev/null 38.3 +++ b/browser/tests/flamingo.pt 38.4 @@ -0,0 +1,2 @@ 38.5 +<p tal:content="context/mymethod">Replaced</p> 38.6 +<p tal:content="python:context.mymethod()">Replaced</p>
39.1 new file mode 100644 39.2 --- /dev/null 39.3 +++ b/browser/tests/framework.py 39.4 @@ -0,0 +1,107 @@ 39.5 +############################################################################## 39.6 +# 39.7 +# ZopeTestCase 39.8 +# 39.9 +# COPY THIS FILE TO YOUR 'tests' DIRECTORY. 39.10 +# 39.11 +# This version of framework.py will use the SOFTWARE_HOME 39.12 +# environment variable to locate Zope and the Testing package. 39.13 +# 39.14 +# If the tests are run in an INSTANCE_HOME installation of Zope, 39.15 +# Products.__path__ and sys.path with be adjusted to include the 39.16 +# instance's Products and lib/python directories respectively. 39.17 +# 39.18 +# If you explicitly set INSTANCE_HOME prior to running the tests, 39.19 +# auto-detection is disabled and the specified path will be used 39.20 +# instead. 39.21 +# 39.22 +# If the 'tests' directory contains a custom_zodb.py file, INSTANCE_HOME 39.23 +# will be adjusted to use it. 39.24 +# 39.25 +# If you set the ZEO_INSTANCE_HOME environment variable a ZEO setup 39.26 +# is assumed, and you can attach to a running ZEO server (via the 39.27 +# instance's custom_zodb.py). 39.28 +# 39.29 +############################################################################## 39.30 +# 39.31 +# The following code should be at the top of every test module: 39.32 +# 39.33 +# import os, sys 39.34 +# if __name__ == '__main__': 39.35 +# execfile(os.path.join(sys.path[0], 'framework.py')) 39.36 +# 39.37 +# ...and the following at the bottom: 39.38 +# 39.39 +# if __name__ == '__main__': 39.40 +# framework() 39.41 +# 39.42 +############################################################################## 39.43 + 39.44 +__version__ = '0.2.3' 39.45 + 39.46 +# Save start state 39.47 +# 39.48 +__SOFTWARE_HOME = os.environ.get('SOFTWARE_HOME', '') 39.49 +__INSTANCE_HOME = os.environ.get('INSTANCE_HOME', '') 39.50 + 39.51 +if __SOFTWARE_HOME.endswith(os.sep): 39.52 + __SOFTWARE_HOME = os.path.dirname(__SOFTWARE_HOME) 39.53 + 39.54 +if __INSTANCE_HOME.endswith(os.sep): 39.55 + __INSTANCE_HOME = os.path.dirname(__INSTANCE_HOME) 39.56 + 39.57 +# Find and import the Testing package 39.58 +# 39.59 +if not sys.modules.has_key('Testing'): 39.60 + p0 = sys.path[0] 39.61 + if p0 and __name__ == '__main__': 39.62 + os.chdir(p0) 39.63 + p0 = '' 39.64 + s = __SOFTWARE_HOME 39.65 + p = d = s and s or os.getcwd() 39.66 + while d: 39.67 + if os.path.isdir(os.path.join(p, 'Testing')): 39.68 + zope_home = os.path.dirname(os.path.dirname(p)) 39.69 + sys.path[:1] = [p0, p, zope_home] 39.70 + break 39.71 + p, d = s and ('','') or os.path.split(p) 39.72 + else: 39.73 + print 'Unable to locate Testing package.', 39.74 + print 'You might need to set SOFTWARE_HOME.' 39.75 + sys.exit(1) 39.76 + 39.77 +import Testing, unittest 39.78 +execfile(os.path.join(os.path.dirname(Testing.__file__), 'common.py')) 39.79 + 39.80 +# Include ZopeTestCase support 39.81 +# 39.82 +if 1: # Create a new scope 39.83 + 39.84 + p = os.path.join(os.path.dirname(Testing.__file__), 'ZopeTestCase') 39.85 + 39.86 + if not os.path.isdir(p): 39.87 + print 'Unable to locate ZopeTestCase package.', 39.88 + print 'You might need to install ZopeTestCase.' 39.89 + sys.exit(1) 39.90 + 39.91 + ztc_common = 'ztc_common.py' 39.92 + ztc_common_global = os.path.join(p, ztc_common) 39.93 + 39.94 + f = 0 39.95 + if os.path.exists(ztc_common_global): 39.96 + execfile(ztc_common_global) 39.97 + f = 1 39.98 + if os.path.exists(ztc_common): 39.99 + execfile(ztc_common) 39.100 + f = 1 39.101 + 39.102 + if not f: 39.103 + print 'Unable to locate %s.' % ztc_common 39.104 + sys.exit(1) 39.105 + 39.106 +# Debug 39.107 +# 39.108 +print 'SOFTWARE_HOME: %s' % os.environ.get('SOFTWARE_HOME', 'Not set') 39.109 +print 'INSTANCE_HOME: %s' % os.environ.get('INSTANCE_HOME', 'Not set') 39.110 +sys.stdout.flush() 39.111 +
40.1 new file mode 100644 40.2 --- /dev/null 40.3 +++ b/browser/tests/i18n.pt 40.4 @@ -0,0 +1,12 @@ 40.5 +<html i18n:domain="fivetest"> 40.6 +<body> 40.7 +<p i18n:translate="">This is a message</p> 40.8 +<p i18n:translate="explicit-msg">This is an explicit message</p> 40.9 +<p i18n:translate="">These are <span tal:replace="python:4" i18n:name="number" /> messages</p> 40.10 +<p i18n:translate="explicit-msgs">These are <span tal:replace="python:4" i18n:name="number" /> explicit messages</p> 40.11 +<table summary="This is an attribute" i18n:attributes="summary"> 40.12 +</table> 40.13 +<table summary="Explicit summary" title="Explicit title" i18n:attributes="summary explicit-summary; title explicit-title" > 40.14 +</table> 40.15 +</body> 40.16 +</html>
41.1 new file mode 100644 41.2 --- /dev/null 41.3 +++ b/browser/tests/menu.zcml 41.4 @@ -0,0 +1,79 @@ 41.5 +<configure xmlns="http://namespaces.zope.org/zope" 41.6 + xmlns:meta="http://namespaces.zope.org/meta" 41.7 + xmlns:browser="http://namespaces.zope.org/browser" 41.8 + i18n_domain="fivetest"> 41.9 + 41.10 + <!-- make the zope2.Public permission work --> 41.11 + <meta:redefinePermission from="zope2.Public" to="zope.Public" /> 41.12 + 41.13 + <!-- browser menu support --> 41.14 + <browser:menu 41.15 + id="testmenu" 41.16 + title="Test menu" 41.17 + /> 41.18 + 41.19 + <browser:menuItem 41.20 + for="OFS.interfaces.IFolder" 41.21 + menu="testmenu" 41.22 + title="Test Menu Item" 41.23 + action="seagull.html" 41.24 + description="This is a test menu item" 41.25 + permission="zope2.Public" 41.26 + /> 41.27 + 41.28 + <browser:menuItem 41.29 + for="OFS.interfaces.IFolder" 41.30 + menu="testmenu" 41.31 + title="Protected Test Menu Item" 41.32 + action="seagull.html" 41.33 + description="This is a protected test menu item" 41.34 + permission="zope2.ViewManagementScreens" 41.35 + /> 41.36 + 41.37 + <browser:menuItems 41.38 + for="OFS.interfaces.IFolder" 41.39 + menu="testmenu"> 41.40 + 41.41 + <menuItem 41.42 + title="Test Menu Item 2" 41.43 + action="parakeet.html" 41.44 + description="This is a test menu item" 41.45 + permission="zope2.Public" 41.46 + /> 41.47 + 41.48 + <menuItem 41.49 + title="Test Menu Item 3" 41.50 + action="falcon.html" 41.51 + description="This is a test menu item" 41.52 + permission="zope2.Public" 41.53 + /> 41.54 + 41.55 + <menuItem 41.56 + title="Protected Test Menu Item 2" 41.57 + action="falcon.html" 41.58 + description="This is a protected test menu item" 41.59 + permission="zope2.ViewManagementScreens" 41.60 + /> 41.61 + 41.62 + </browser:menuItems> 41.63 + 41.64 + <!-- page in a menu --> 41.65 + <browser:page 41.66 + for="OFS.interfaces.IFolder" 41.67 + template="cockatiel.pt" 41.68 + name="cockatiel_menu_public.html" 41.69 + permission="zope2.Public" 41.70 + title="Page in a menu (public)" 41.71 + menu="testmenu" 41.72 + /> 41.73 + 41.74 + <browser:page 41.75 + for="OFS.interfaces.IFolder" 41.76 + template="cockatiel.pt" 41.77 + name="cockatiel_menu_protected.html" 41.78 + permission="zope2.ViewManagementScreens" 41.79 + title="Page in a menu (protected)" 41.80 + menu="testmenu" 41.81 + /> 41.82 + 41.83 +</configure> 41.84 \ No newline at end of file
42.1 new file mode 100644 42.2 --- /dev/null 42.3 +++ b/browser/tests/ostrich.pt 42.4 @@ -0,0 +1,6 @@ 42.5 +<ul> 42.6 +<li tal:repeat="item python:['Alpha', 'Beta', 'Gamma']" tal:content="item"/> 42.7 +</ul> 42.8 +<ul> 42.9 +<li tal:repeat="item python:['Alpha', 'Beta', 'Gamma']" tal:content="python:repeat['item'].index"/> 42.10 +</ul>
43.1 new file mode 100644 43.2 --- /dev/null 43.3 +++ b/browser/tests/overrides.zcml 43.4 @@ -0,0 +1,13 @@ 43.5 +<configure xmlns="http://namespaces.zope.org/zope" 43.6 + xmlns:browser="http://namespaces.zope.org/browser"> 43.7 + 43.8 + <!-- mouse instead of eagle --> 43.9 + <browser:page 43.10 + for="Products.Five.tests.testing.simplecontent.ISimpleContent" 43.11 + class=".pages.SimpleView" 43.12 + attribute="mouse" 43.13 + name="overridden_view" 43.14 + permission="zope2.Public" 43.15 + /> 43.16 + 43.17 +</configure>
44.1 new file mode 100644 44.2 --- /dev/null 44.3 +++ b/browser/tests/owl.pt 44.4 @@ -0,0 +1,1 @@ 44.5 +<p tal:content="python:1+1">Some content</p> 44.6 \ No newline at end of file
45.1 new file mode 100644 45.2 --- /dev/null 45.3 +++ b/browser/tests/pages.py 45.4 @@ -0,0 +1,67 @@ 45.5 +############################################################################## 45.6 +# 45.7 +# Copyright (c) 2004, 2005 Zope Corporation and Contributors. 45.8 +# All Rights Reserved. 45.9 +# 45.10 +# This software is subject to the provisions of the Zope Public License, 45.11 +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 45.12 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 45.13 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 45.14 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 45.15 +# FOR A PARTICULAR PURPOSE. 45.16 +# 45.17 +############################################################################## 45.18 +"""Test browser pages 45.19 + 45.20 +$Id: pages.py 12884 2005-05-30 13:10:41Z philikon $ 45.21 +""" 45.22 +from Products.Five import BrowserView 45.23 + 45.24 +class SimpleView(BrowserView): 45.25 + """More docstring. Please Zope""" 45.26 + 45.27 + def eagle(self): 45.28 + """Docstring""" 45.29 + return "The eagle has landed" 45.30 + 45.31 + def mouse(self): 45.32 + """Docstring""" 45.33 + return "The mouse has been eaten by the eagle" 45.34 + 45.35 +class FancyView(BrowserView): 45.36 + """Fancy, fancy stuff""" 45.37 + 45.38 + def view(self): 45.39 + return "Fancy, fancy" 45.40 + 45.41 +class CallableNoDocstring: 45.42 + 45.43 + def __call__(self): 45.44 + return "No docstring" 45.45 + 45.46 +def function_no_docstring(self): 45.47 + return "No docstring" 45.48 + 45.49 +class NoDocstringView(BrowserView): 45.50 + 45.51 + def method(self): 45.52 + return "No docstring" 45.53 + 45.54 + function = function_no_docstring 45.55 + 45.56 + object = CallableNoDocstring() 45.57 + 45.58 +class NewStyleClass(object): 45.59 + """ 45.60 + This is a testclass to verify that new style classes are ignored 45.61 + in browser:page 45.62 + """ 45.63 + 45.64 + def __init__(self, context, request): 45.65 + """Docstring""" 45.66 + self.context = context 45.67 + self.request = request 45.68 + 45.69 + def method(self): 45.70 + """Docstring""" 45.71 + return
46.1 new file mode 100644 46.2 --- /dev/null 46.3 +++ b/browser/tests/pages.txt 46.4 @@ -0,0 +1,316 @@ 46.5 +Test browser pages 46.6 +================== 46.7 + 46.8 +Let's register a quite large amount of test pages: 46.9 + 46.10 + >>> import Products.Five.browser.tests 46.11 + >>> from Products.Five import zcml 46.12 + >>> zcml.load_config("configure.zcml", Products.Five) 46.13 + >>> zcml.load_config('pages.zcml', package=Products.Five.browser.tests) 46.14 + 46.15 +Let's add a test object that we view most of the pages off of: 46.16 + 46.17 + >>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent 46.18 + >>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid') 46.19 + 46.20 +We also need to create a stub user account and login; otherwise we 46.21 +wouldn't have all the rights to do traversal etc.: 46.22 + 46.23 + >>> uf = self.folder.acl_users 46.24 + >>> uf._doAddUser('manager', 'r00t', ['Manager'], []) 46.25 + >>> self.login('manager') 46.26 + 46.27 +Now for some actual testing... 46.28 + 46.29 + 46.30 +Simple pages 46.31 +------------ 46.32 + 46.33 +A browser page that is a view class's attribute (method): 46.34 + 46.35 + >>> view = self.folder.unrestrictedTraverse('testoid/eagle.txt') 46.36 + >>> view is not None 46.37 + True 46.38 + >>> from Products.Five.browser.tests.pages import SimpleView 46.39 + >>> isinstance(view, SimpleView) 46.40 + True 46.41 + >>> view() 46.42 + 'The eagle has landed' 46.43 + 46.44 +A browser page that is a Page Template. 46.45 + 46.46 + >>> view = self.folder.unrestrictedTraverse('testoid/owl.html') 46.47 + >>> view() 46.48 + '<p>2</p>\n' 46.49 + 46.50 +A browser page that is a PageTemplate plus a view class: 46.51 + 46.52 + >>> view = self.folder.unrestrictedTraverse('testoid/falcon.html') 46.53 + >>> isinstance(view, SimpleView) 46.54 + True 46.55 + >>> view() 46.56 + '<p>The falcon has taken flight</p>\n' 46.57 + 46.58 +Test pages that have been registered through the cumulative 46.59 +<browser:pages> directive: 46.60 + 46.61 + >>> view = self.folder.unrestrictedTraverse('testoid/eagle-page.txt') 46.62 + >>> isinstance(view, SimpleView) 46.63 + True 46.64 + >>> view() 46.65 + 'The eagle has landed' 46.66 + 46.67 + >>> view = self.folder.unrestrictedTraverse('testoid/mouse-page.txt') 46.68 + >>> isinstance(view, SimpleView) 46.69 + True 46.70 + >>> view() 46.71 + 'The mouse has been eaten by the eagle' 46.72 + 46.73 +Zope 2 objects always need a docstring in order to be published. Five 46.74 +adds a docstring automatically if a view method doesn't have it, but 46.75 +it shouldn't modify existing ones: 46.76 + 46.77 + >>> view = self.folder.unrestrictedTraverse('testoid/eagle.txt') 46.78 + >>> view.eagle.__doc__ == SimpleView.eagle.__doc__ 46.79 + True 46.80 + 46.81 +Test whether new-style classes are ignored when registering browser 46.82 +pages with view classes. When traversing for a non-existing view, we 46.83 +should get an AttributeError: 46.84 + 46.85 + >>> self.folder.unrestrictedTraverse('testoid/@@new_style_class') 46.86 + Traceback (most recent call last): 46.87 + ... 46.88 + AttributeError: @@new_style_class 46.89 + 46.90 + 46.91 +ZPT-based browser pages 46.92 +----------------------- 46.93 + 46.94 +Test access to ``context`` from ZPTs: 46.95 + 46.96 + >>> view = self.folder.unrestrictedTraverse('testoid/flamingo.html') 46.97 + >>> print view() 46.98 + <p>Hello world</p> 46.99 + <p>Hello world</p> 46.100 + 46.101 +Test macro access from ZPT pages: 46.102 + 46.103 + >>> view = self.folder.unrestrictedTraverse('testoid/seagull.html') 46.104 + >>> view() 46.105 + '<html><head><title>bird macro</title></head><body>Color: gray</body></html>\n' 46.106 + 46.107 +Test whether old-style direct traversal still works with a 46.108 +five:traversable class: 46.109 + 46.110 + >>> old_view = self.folder.unrestrictedTraverse('testoid/direct') 46.111 + >>> old_view() 46.112 + 'Direct traversal worked' 46.113 + 46.114 +test_zpt_things: 46.115 + 46.116 + >>> view = self.folder.unrestrictedTraverse('testoid/condor.html') 46.117 + >>> print view() 46.118 + <p>Hello world</p> 46.119 + <p>The eagle has landed</p> 46.120 + <p>Hello world</p> 46.121 + <p>Hello world</p> 46.122 + 46.123 +Make sure that tal:repeat works in ZPT browser pages: 46.124 + 46.125 + >>> view = self.folder.unrestrictedTraverse('testoid/ostrich.html') 46.126 + >>> print view() 46.127 + <ul> 46.128 + <li>Alpha</li> 46.129 + <li>Beta</li> 46.130 + <li>Gamma</li> 46.131 + </ul> 46.132 + <ul> 46.133 + <li>0</li> 46.134 + <li>1</li> 46.135 + <li>2</li> 46.136 + </ul> 46.137 + 46.138 +Test TALES traversal in ZPT pages: 46.139 + 46.140 + >>> view = self.folder.unrestrictedTraverse('testoid/tales_traversal.html') 46.141 + >>> print view() 46.142 + <p>testoid</p> 46.143 + <p>test_folder_1_</p> 46.144 + 46.145 +Make sure that global template variables in ZPT pages are correct: 46.146 + 46.147 + >>> view = self.folder.unrestrictedTraverse('testoid/template_variables.html') 46.148 + >>> print view() 46.149 + View is a view: True 46.150 + Context is testoid: True 46.151 + Contaxt.aq_parent is test_folder_1_: True 46.152 + Container is context: True 46.153 + Here is context: True 46.154 + Nothing is None: True 46.155 + Default works: True 46.156 + Root is the application: True 46.157 + Template is a template: True 46.158 + Traverse_subpath exists and is empty: True 46.159 + Request is a request: True 46.160 + User is manager: True 46.161 + Options exist: True 46.162 + Attrs exist: True 46.163 + Repeat exists: True 46.164 + Loop exists: True 46.165 + Modules exists: True 46.166 + 46.167 +Make sure that ZPT's aren't a security-less zone. Let's logout and 46.168 +try to access some protected stuff. Let's not forgot to login again, 46.169 +of course: 46.170 + 46.171 + >>> from AccessControl import allow_module 46.172 + >>> allow_module('smtpd') 46.173 + >>> self.logout() 46.174 + >>> view = self.folder.unrestrictedTraverse('testoid/security.html') 46.175 + >>> print view() 46.176 + <div>NoneType</div> 46.177 + <div>smtpd</div> 46.178 + >>> self.login('manager') 46.179 + 46.180 +Test pages registered through the <five:pagesFromDirectory /> directive: 46.181 + 46.182 + >>> view = self.folder.unrestrictedTraverse('testoid/dirpage1') 46.183 + >>> print view() 46.184 + <html> 46.185 + <p>This is page 1</p> 46.186 + </html> 46.187 + 46.188 + >>> view = self.folder.unrestrictedTraverse('testoid/dirpage2') 46.189 + >>> print view() 46.190 + <html> 46.191 + <p>This is page 2</p> 46.192 + </html> 46.193 + 46.194 + 46.195 +Low-level security 46.196 +------------------ 46.197 + 46.198 +This tests security on a low level (functional pages test has 46.199 +high-level security tests). Let's manually look up a protected view: 46.200 + 46.201 + >>> from Products.Five.traversable import FakeRequest 46.202 + >>> from zope.app import zapi 46.203 + >>> request = FakeRequest() 46.204 + >>> view = zapi.getView(self.folder.testoid, 'eagle.txt', request) 46.205 + 46.206 +It's protecting the object with the permission, and not the attribute, 46.207 +so we get ('',) instead of ('eagle',): 46.208 + 46.209 + >>> getattr(view, '__ac_permissions__') 46.210 + (('View management screens', ('',)),) 46.211 + 46.212 +Wrap into an acquisition so that imPermissionRole objects can be 46.213 +evaluated. __roles__ is a imPermissionRole object: 46.214 + 46.215 + >>> view = view.__of__(self.folder.testoid) 46.216 + >>> view_roles = getattr(view, '__roles__', None) 46.217 + >>> view_roles 46.218 + ('Manager',) 46.219 + 46.220 +Check to see if view's context properly acquires its true 46.221 +parent 46.222 + 46.223 + >>> from Acquisition import aq_parent, aq_base, aq_inner 46.224 + >>> context = getattr(view, 'context') 46.225 + 46.226 +Check the wrapper type 46.227 + 46.228 + >>> from Acquisition import ImplicitAcquisitionWrapper 46.229 + >>> type(context) == ImplicitAcquisitionWrapper 46.230 + True 46.231 + 46.232 +The acquired parent is the view. This isn't 46.233 +usually what you want. 46.234 + 46.235 + >>> aq_parent(context) == view 46.236 + True 46.237 + 46.238 +To get what you usually want, do this 46.239 + 46.240 + >>> context.aq_inner.aq_parent 46.241 + <Folder at /test_folder_1_> 46.242 + 46.243 +C methods work the same 46.244 + 46.245 + >>> aq_parent(aq_inner(context)) 46.246 + <Folder at /test_folder_1_> 46.247 + 46.248 +High-level security 46.249 +------------------- 46.250 + 46.251 + >>> protected_view_names = [ 46.252 + ... 'eagle.txt', 'falcon.html', 'owl.html', 'flamingo.html', 46.253 + ... 'condor.html', 'protectededitform.html'] 46.254 + >>> 46.255 + >>> public_view_names = [ 46.256 + ... 'public_attribute_page', 46.257 + ... 'public_template_page', 46.258 + ... 'public_template_class_page', 46.259 + ... 'nodoc-method', 'nodoc-function', 'nodoc-object', 46.260 + ... 'dirpage1', 'dirpage2'] 46.261 + 46.262 + >>> from Products.Five.tests.testing.restricted import checkRestricted 46.263 + >>> from Products.Five.tests.testing.restricted import checkUnauthorized 46.264 + 46.265 +As long as we're not authenticated, we should get Unauthorized for 46.266 +protected views, but we should be able to view the public ones: 46.267 + 46.268 + >>> self.logout() 46.269 + >>> for view_name in protected_view_names: 46.270 + ... checkUnauthorized( 46.271 + ... self.folder, 46.272 + ... 'context.restrictedTraverse("testoid/%s")()' % view_name) 46.273 + 46.274 + >>> for view_name in public_view_names: 46.275 + ... checkRestricted( 46.276 + ... self.folder, 46.277 + ... 'context.restrictedTraverse("testoid/%s")()' % view_name) 46.278 + >>> self.login('manager') 46.279 + 46.280 +Being logged in as a manager again, we find that the protected pages 46.281 +are not accessible to us: 46.282 + 46.283 + >>> for view_name in protected_view_names: 46.284 + ... checkRestricted( 46.285 + ... self.folder, 46.286 + ... 'context.restrictedTraverse("testoid/%s")()' % view_name) 46.287 + 46.288 + >>> checkRestricted( 46.289 + ... self.folder, 46.290 + ... 'context.restrictedTraverse("testoid/eagle.method").eagle()') 46.291 + 46.292 + 46.293 +Other 46.294 +----- 46.295 + 46.296 +Make sure that browser pages can be overridden: 46.297 + 46.298 + >>> zcml.load_string(''' 46.299 + ... <includeOverrides 46.300 + ... package="Products.Five.browser.tests" 46.301 + ... file="overrides.zcml" /> 46.302 + ... ''') 46.303 + >>> view = self.folder.unrestrictedTraverse('testoid/overridden_view') 46.304 + >>> view() 46.305 + 'The mouse has been eaten by the eagle' 46.306 + 46.307 +Test traversal to resources from within ZPT pages: 46.308 + 46.309 + >>> zcml.load_config('resource.zcml', package=Products.Five.browser.tests) 46.310 + >>> view = self.folder.unrestrictedTraverse('testoid/parakeet.html') 46.311 + >>> print view() 46.312 + <html><body><img alt="" 46.313 + src="http://nohost/test_folder_1_/testoid/++resource++pattern.png" /></body></html> 46.314 + 46.315 + 46.316 +Clean up 46.317 +-------- 46.318 + 46.319 + >>> from zope.app.tests.placelesssetup import tearDown 46.320 + >>> tearDown()
47.1 new file mode 100644 47.2 --- /dev/null 47.3 +++ b/browser/tests/pages.zcml 47.4 @@ -0,0 +1,224 @@ 47.5 +<configure xmlns="http://namespaces.zope.org/zope" 47.6 + xmlns:meta="http://namespaces.zope.org/meta" 47.7 + xmlns:browser="http://namespaces.zope.org/browser" 47.8 + xmlns:five="http://namespaces.zope.org/five"> 47.9 + 47.10 + <!-- make the zope2.Public permission work --> 47.11 + <meta:redefinePermission from="zope2.Public" to="zope.Public" /> 47.12 + 47.13 + <!-- attribute page --> 47.14 + <browser:page 47.15 + for="Products.Five.tests.testing.simplecontent.ISimpleContent" 47.16 + class=".pages.SimpleView" 47.17 + attribute="eagle" 47.18 + name="eagle.txt" 47.19 + permission="zope2.ViewManagementScreens" 47.20 + /> 47.21 + 47.22 + <browser:page 47.23 + for="Products.Five.tests.testing.simplecontent.ISimpleContent" 47.24 + class=".pages.SimpleView" 47.25 + name="eagle.method" 47.26 + permission="zope2.ViewManagementScreens" 47.27 + allowed_attributes="eagle" 47.28 + /> 47.29 + 47.30 + <!-- attribute page --> 47.31 + <browser:pages 47.32 + for="Products.Five.tests.testing.simplecontent.ISimpleContent" 47.33 + class=".pages.SimpleView" 47.34 + permission="zope2.ViewManagementScreens" 47.35 + > 47.36 + <browser:page 47.37 + name="eagle-page.txt" 47.38 + attribute="eagle" 47.39 + /> 47.40 + <browser:page 47.41 + name="mouse-page.txt" 47.42 + attribute="mouse" 47.43 + /> 47.44 + </browser:pages> 47.45 + 47.46 + <!-- template/class page --> 47.47 + <browser:page 47.48 + for="Products.Five.tests.testing.simplecontent.ISimpleContent" 47.49 + class=".pages.SimpleView" 47.50 + template="falcon.pt" 47.51 + name="falcon.html" 47.52 + permission="zope2.ViewManagementScreens" 47.53 + /> 47.54 + 47.55 + <!-- template page (with simple python expression) --> 47.56 + <browser:page 47.57 + for="Products.Five.tests.testing.simplecontent.ISimpleContent" 47.58 + template="owl.pt" 47.59 + name="owl.html" 47.60 + permission="zope2.ViewManagementScreens" 47.61 + /> 47.62 + 47.63 + <!-- template page which calls on context using python and path 47.64 + expressions --> 47.65 + <browser:page 47.66 + for="Products.Five.tests.testing.simplecontent.ISimpleContent" 47.67 + template="flamingo.pt" 47.68 + name="flamingo.html" 47.69 + permission="zope2.ViewManagementScreens" 47.70 + /> 47.71 + 47.72 + <!-- template/class page which calls on context, view, views --> 47.73 + <browser:page 47.74 + for="Products.Five.tests.testing.simplecontent.ISimpleContent" 47.75 + class=".pages.SimpleView" 47.76 + template="condor.pt" 47.77 + name="condor.html" 47.78 + permission="zope2.ViewManagementScreens" 47.79 + /> 47.80 + 47.81 + <!-- template page that defines a macro page --> 47.82 + <browser:page 47.83 + for="Products.Five.tests.testing.simplecontent.ISimpleContent" 47.84 + template="birdmacro.pt" 47.85 + name="bird.html" 47.86 + permission="zope2.ViewManagementScreens" 47.87 + /> 47.88 + 47.89 + <!-- template page that uses macro page --> 47.90 + <browser:page 47.91 + for="Products.Five.tests.testing.simplecontent.ISimpleContent" 47.92 + template="seagull.pt" 47.93 + name="seagull.html" 47.94 + permission="zope2.ViewManagementScreens" 47.95 + /> 47.96 + 47.97 + <!-- test TALES --> 47.98 + <browser:page 47.99 + for="Products.Five.tests.testing.simplecontent.ISimpleContent" 47.100 + template="ostrich.pt" 47.101 + name="ostrich.html" 47.102 + permission="zope2.ViewManagementScreens" 47.103 + /> 47.104 + 47.105 + <browser:page 47.106 + for="Products.Five.tests.testing.simplecontent.ISimpleContent" 47.107 + template="tales_traversal.pt" 47.108 + name="tales_traversal.html" 47.109 + permission="zope2.ViewManagementScreens" 47.110 + /> 47.111 + 47.112 + <browser:page 47.113 + for="Products.Five.tests.testing.simplecontent.ISimpleContent" 47.114 + template="template_variables.pt" 47.115 + name="template_variables.html" 47.116 + permission="zope2.ViewManagementScreens" 47.117 + /> 47.118 + 47.119 + <!-- template security --> 47.120 + 47.121 + <browser:page 47.122 + for="Products.Five.tests.testing.simplecontent.ISimpleContent" 47.123 + template="security.pt" 47.124 + name="security.html" 47.125 + permission="zope2.View" 47.126 + /> 47.127 + 47.128 + <!-- a publicly accessible page, attribute, template, template/class --> 47.129 + 47.130 + <browser:page 47.131 + for="Products.Five.tests.testing.simplecontent.ISimpleContent" 47.132 + class=".pages.SimpleView" 47.133 + attribute="eagle" 47.134 + name="public_attribute_page" 47.135 + permission="zope2.Public" 47.136 + /> 47.137 + 47.138 + <browser:page 47.139 + for="Products.Five.tests.testing.simplecontent.ISimpleContent" 47.140 + template="owl.pt" 47.141 + name="public_template_page" 47.142 + permission="zope2.Public" 47.143 + /> 47.144 + 47.145 + <browser:page 47.146 + for="Products.Five.tests.testing.simplecontent.ISimpleContent" 47.147 + class=".pages.SimpleView" 47.148 + template="falcon.pt" 47.149 + name="public_template_class_page" 47.150 + permission="zope2.Public" 47.151 + /> 47.152 + 47.153 + <browser:page 47.154 + for="Products.Five.tests.testing.simplecontent.ISimpleContent" 47.155 + class=".pages.SimpleView" 47.156 + template="parakeet.pt" 47.157 + name="parakeet.html" 47.158 + permission="zope2.ViewManagementScreens" 47.159 + /> 47.160 + 47.161 + <!-- pages from methods/functions/callables that don't have docstrings --> 47.162 + <browser:pages 47.163 + for="Products.Five.tests.testing.simplecontent.ISimpleContent" 47.164 + class="Products.Five.browser.tests.pages.NoDocstringView" 47.165 + permission="zope2.Public"> 47.166 + <browser:page 47.167 + name="nodoc-method" 47.168 + attribute="method" 47.169 + /> 47.170 + <browser:page 47.171 + name="nodoc-function" 47.172 + attribute="function" 47.173 + /> 47.174 + <browser:page 47.175 + name="nodoc-object" 47.176 + attribute="object" 47.177 + /> 47.178 + </browser:pages> 47.179 + 47.180 + <!-- five:pagesFromDirectory loads all .pt files in a directory as pages. 47.181 + This is mainly used to load Zope2 skin templates so they can be used 47.182 + in five skins and layers. --> 47.183 + <five:pagesFromDirectory 47.184 + for="Products.Five.tests.testing.simplecontent.ISimpleContent" 47.185 + module="Products.Five.browser.tests" 47.186 + directory="pages" 47.187 + permission="zope2.Public" 47.188 + /> 47.189 + 47.190 + <!-- browser:page directives with new style classes are ignored --> 47.191 + 47.192 + <browser:page 47.193 + for="Products.Five.tests.testing.simplecontent.ISimpleContent" 47.194 + class=".pages.NewStyleClass" 47.195 + name="new_style_class" 47.196 + attribute="method" 47.197 + permission="zope2.Public" 47.198 + /> 47.199 + 47.200 + <!-- Verify that browser:view works, especially when no specific 47.201 + view attribute is specified --> 47.202 + 47.203 + <browser:view 47.204 + name="" 47.205 + for="Products.Five.tests.testing.simplecontent.ISimpleContent" 47.206 + class=".pages.SimpleView" 47.207 + permission="zope2.Public" 47.208 + /> 47.209 + 47.210 + <!-- XXX this should really be in Five.form.tests --> 47.211 + 47.212 + <!-- protected edit form for permission check --> 47.213 + <browser:editform 47.214 + schema="Products.Five.tests.testing.simplecontent.ISimpleContent" 47.215 + name="protectededitform.html" 47.216 + permission="zope2.ViewManagementScreens" 47.217 + /> 47.218 + 47.219 + <!-- stuff that we'll override in overrides.zcml --> 47.220 + <browser:page 47.221 + for="Products.Five.tests.testing.simplecontent.ISimpleContent" 47.222 + class=".pages.SimpleView" 47.223 + attribute="eagle" 47.224 + name="overridden_view" 47.225 + permission="zope2.Public" 47.226 + /> 47.227 + 47.228 +</configure>
48.1 new file mode 100644 48.2 --- /dev/null 48.3 +++ b/browser/tests/pages/dirpage1.pt 48.4 @@ -0,0 +1,3 @@ 48.5 +<html> 48.6 +<p>This is page 1</p> 48.7 +</html>
49.1 new file mode 100644 49.2 --- /dev/null 49.3 +++ b/browser/tests/pages/dirpage2.pt 49.4 @@ -0,0 +1,3 @@ 49.5 +<html> 49.6 +<p>This is page 2</p> 49.7 +</html>
50.1 new file mode 100644 50.2 --- /dev/null 50.3 +++ b/browser/tests/pages_ftest.txt 50.4 @@ -0,0 +1,129 @@ 50.5 +Functional Browser Pages Test 50.6 +============================= 50.7 + 50.8 +This test tests publishing aspects of browser pages. Let's register 50.9 +some: 50.10 + 50.11 + >>> import Products.Five.browser.tests 50.12 + >>> from Products.Five import zcml 50.13 + >>> zcml.load_config("configure.zcml", Products.Five) 50.14 + >>> zcml.load_config('pages.zcml', package=Products.Five.browser.tests) 50.15 + 50.16 +Let's also add one of our stub objects to play with: 50.17 + 50.18 + >>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent 50.19 + >>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid') 50.20 + 50.21 + 50.22 +Docstrings 50.23 +---------- 50.24 + 50.25 +In Zope 2, objects normally have to have a docstring in order to be 50.26 +published. This crazy requirement luckily isn't true for Zope 3, so 50.27 +it should be possible to write docstring-less view classes that are 50.28 +still published through ZPublisher. 50.29 + 50.30 +We see that even though the callables have no docstring, they are 50.31 +published nevertheless: 50.32 + 50.33 + >>> print http(r""" 50.34 + ... GET /test_folder_1_/testoid/nodoc-function HTTP/1.1 50.35 + ... """) 50.36 + HTTP/1.1 200 OK 50.37 + ... 50.38 + No docstring 50.39 + 50.40 + >>> print http(r""" 50.41 + ... GET /test_folder_1_/testoid/nodoc-method HTTP/1.1 50.42 + ... """) 50.43 + HTTP/1.1 200 OK 50.44 + ... 50.45 + No docstring 50.46 + 50.47 + >>> print http(r""" 50.48 + ... GET /test_folder_1_/testoid/nodoc-object HTTP/1.1 50.49 + ... """) 50.50 + HTTP/1.1 200 OK 50.51 + ... 50.52 + No docstring 50.53 + 50.54 + 50.55 +Security 50.56 +-------- 50.57 + 50.58 +Browser pages need to be protected with a permission. Let's test 50.59 +those; we start by adding two users: 50.60 + 50.61 + >>> uf = self.folder.acl_users 50.62 + >>> uf._doAddUser('viewer', 'secret', [], []) 50.63 + >>> uf._doAddUser('manager', 'r00t', ['Manager'], []) 50.64 + 50.65 + >>> protected_view_names = [ 50.66 + ... 'eagle.txt', 'falcon.html', 'owl.html', 'flamingo.html', 50.67 + ... 'condor.html', 'protectededitform.html'] 50.68 + >>> 50.69 + >>> public_view_names = [ 50.70 + ... 'public_attribute_page', 50.71 + ... 'public_template_page', 50.72 + ... 'public_template_class_page', 50.73 + ... 'nodoc-method', 'nodoc-function', 'nodoc-object', 50.74 + ... 'dirpage1', 'dirpage2'] 50.75 + >>> 50.76 + >>> ViewManagementScreens = 'View management screens' 50.77 + 50.78 +As a normal user we shouldn't get to see those pages protected with 50.79 +the 'View management screens' permission. Thus we expect a 401 50.80 +Unauthorized: 50.81 + 50.82 + >>> for view_name in protected_view_names: 50.83 + ... response = self.publish('/test_folder_1_/testoid/%s' % view_name, 50.84 + ... basic='viewer:secret') 50.85 + ... status = response.getStatus() 50.86 + ... self.failUnless(status == 401, (status, 401, view_name)) 50.87 + 50.88 +The same should apply for the user if he has all other permissions 50.89 +except 'View management screens': 50.90 + 50.91 + >>> permissions = self.folder.possible_permissions() 50.92 + >>> permissions.remove(ViewManagementScreens) 50.93 + >>> self.folder._addRole('Viewer') 50.94 + >>> self.folder.manage_role('Viewer', permissions) 50.95 + >>> self.folder.manage_addLocalRoles('viewer', ['Viewer']) 50.96 + 50.97 + >>> for view_name in protected_view_names: 50.98 + ... response = self.publish('/test_folder_1_/testoid/%s' % view_name, 50.99 + ... basic='viewer:secret') 50.100 + ... status = response.getStatus() 50.101 + ... self.failUnless(status == 401, (status, 401, view_name)) 50.102 + 50.103 +If we grant 'View management screens' now, the protected views should 50.104 +become viewable: 50.105 + 50.106 + >>> self.folder.manage_role('Viewer', [ViewManagementScreens]) 50.107 + >>> for view_name in protected_view_names: 50.108 + ... response = self.publish('/test_folder_1_/testoid/%s' % view_name, 50.109 + ... basic='viewer:secret') 50.110 + ... status = response.getStatus() 50.111 + ... self.failUnless(status == 200, (status, 200, view_name)) 50.112 + 50.113 +Managers should always be able to view anything, including proctected 50.114 +stuff: 50.115 + 50.116 + >>> for view_name in protected_view_names: 50.117 + ... response = self.publish('/test_folder_1_/testoid/%s' % view_name, 50.118 + ... basic='manager:r00t') 50.119 + ... self.assertEqual(response.getStatus(), 200) 50.120 + 50.121 +All public views should always be accessible by anyone: 50.122 + 50.123 + >>> for view_name in public_view_names: 50.124 + ... response = self.publish('/test_folder_1_/testoid/%s' % view_name) 50.125 + ... status = response.getStatus() 50.126 + ... self.failUnless(status == 200, (status, 200, view_name)) 50.127 + 50.128 + 50.129 +Clean up 50.130 +-------- 50.131 + 50.132 + >>> from zope.app.tests.placelesssetup import tearDown 50.133 + >>> tearDown()
51.1 new file mode 100644 51.2 --- /dev/null 51.3 +++ b/browser/tests/parakeet.pt 51.4 @@ -0,0 +1,1 @@ 51.5 +<html><body><img alt="" src="" tal:attributes="src context/++resource++pattern.png" /></body></html>
52.1 new file mode 100644 52.2 index 0000000000000000000000000000000000000000..bf5aa2988a1d3d9d8c7cfe967c2d67e42ac1134b 52.3 GIT binary patch 52.4 literal 170 52.5 zc%17D@N?(olHy`uVBq!ia0vp^%plANB6FUp{{d2L0X`wFXU?4Y|NsBxUePk32xoyu 52.6 zWHAE+w=f7ZGR&GI0Tg5}@$_|NzsxNp#xBnvqjv=;BwOMdQR1ARo12<f!r)w#npl#W 52.7 zqEMb$lA#cik*eVC=^OAqMKuqokk8Y_F{I*FazR4Gfd@eA(-;-`c5*HO$}o7k`njxg 52.8 HN@xNA)O0Nk 52.9
53.1 new file mode 100644 53.2 --- /dev/null 53.3 +++ b/browser/tests/pts_test_languages.pt 53.4 @@ -0,0 +1,7 @@ 53.5 +<html> 53.6 +<body> 53.7 +<!-- fivetest is a Zope 3 style i18n domain, default is a PTS domain --> 53.8 +<p i18n:domain="fivetest" i18n:translate="">This is a message</p> 53.9 +<p i18n:domain="PlacelessTranslationService" i18n:translate="">Reload this catalog</p> 53.10 +</body> 53.11 +</html>
54.1 new file mode 100644 54.2 --- /dev/null 54.3 +++ b/browser/tests/pts_test_languages.py 54.4 @@ -0,0 +1,30 @@ 54.5 +############################################################################## 54.6 +# 54.7 +# Copyright (c) 2005 Zope Corporation and Contributors. 54.8 +# All Rights Reserved. 54.9 +# 54.10 +# This software is subject to the provisions of the Zope Public License, 54.11 +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 54.12 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 54.13 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 54.14 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 54.15 +# FOR A PARTICULAR PURPOSE. 54.16 +# 54.17 +############################################################################## 54.18 +"""Test the PTS language integration. 54.19 + 54.20 +$Id: pts_test_languages.py 14400 2005-07-07 17:55:08Z philikon $ 54.21 +""" 54.22 +import os, sys 54.23 +if __name__ == '__main__': 54.24 + execfile(os.path.join(sys.path[0], 'framework.py')) 54.25 + 54.26 +def test_suite(): 54.27 + from Testing.ZopeTestCase import installProduct, FunctionalDocFileSuite 54.28 + installProduct('Five') 54.29 + installProduct('PlacelessTranslationService') 54.30 + return FunctionalDocFileSuite('pts_test_languages.txt', 54.31 + package='Products.Five.browser.tests') 54.32 + 54.33 +if __name__ == '__main__': 54.34 + framework()
55.1 new file mode 100644 55.2 --- /dev/null 55.3 +++ b/browser/tests/pts_test_languages.txt 55.4 @@ -0,0 +1,134 @@ 55.5 +PTS languages 55.6 +============= 55.7 + 55.8 +Before we start, we need to set up a manager user to be able to create 55.9 +the portal: 55.10 + 55.11 + >>> uf = self.folder.acl_users 55.12 + >>> uf._doAddUser('manager', 'r00t', ['Manager'], []) 55.13 + 55.14 +We need to 1) configure the Zope 3 i18n message catalogs, 3) register 55.15 +the PTS languagees adapter and 3) register our test page: 55.16 + 55.17 + >>> configure_zcml = """ 55.18 + ... <configure 55.19 + ... xmlns="http://namespaces.zope.org/zope" 55.20 + ... xmlns:browser="http://namespaces.zope.org/browser" 55.21 + ... xmlns:i18n="http://namespaces.zope.org/i18n" 55.22 + ... > 55.23 + ... <configure package="Products.Five.tests"> 55.24 + ... <i18n:registerTranslations directory="locales" /> 55.25 + ... </configure> 55.26 + ... 55.27 + ... <adapter 55.28 + ... for="zope.publisher.interfaces.http.IHTTPRequest" 55.29 + ... provides="zope.i18n.interfaces.IUserPreferredLanguages" 55.30 + ... factory="Products.Five.i18n.PTSLanguages" 55.31 + ... /> 55.32 + ... 55.33 + ... <configure package="Products.Five.browser.tests"> 55.34 + ... <browser:page 55.35 + ... for="Products.Five.interfaces.IFolder" 55.36 + ... template="pts_test_languages.pt" 55.37 + ... name="pts_test_languages.html" 55.38 + ... permission="zope2.View" 55.39 + ... /> 55.40 + ... </configure> 55.41 + ... </configure> 55.42 + ... """ 55.43 + >>> from Products.Five import zcml 55.44 + >>> zcml.load_string(configure_zcml) 55.45 + 55.46 +Finally, we need a traversable folder so that the test page we 55.47 +registered is found: 55.48 + 55.49 + >>> from Products.Five.tests.testing import manage_addFiveTraversableFolder 55.50 + >>> manage_addFiveTraversableFolder(self.folder, 'ftf') 55.51 + 55.52 +Now for some actual testing... Our test page is a simple ZPT 55.53 +translating two messages from different domains. The first domain is 55.54 +a Zope 3 style one, the second one comes from PTS. 55.55 + 55.56 +Both systems should yield the same default language (English) when no 55.57 +language is specified whatsoever: 55.58 + 55.59 + >>> print http(r""" 55.60 + ... GET /test_folder_1_/ftf/pts_test_languages.html HTTP/1.1 55.61 + ... """) 55.62 + HTTP/1.1 200 OK 55.63 + ... 55.64 + <html> 55.65 + <body> 55.66 + <!-- fivetest is a Zope 3 style i18n domain, default is a PTS domain --> 55.67 + <p>This is a message</p> 55.68 + <p>Reload this catalog</p> 55.69 + </body> 55.70 + </html> 55.71 + 55.72 +Both systems should honour the HTTP ``Accept-Language`` header in the 55.73 +same way: 55.74 + 55.75 + >>> print http(r""" 55.76 + ... GET /test_folder_1_/ftf/pts_test_languages.html HTTP/1.1 55.77 + ... Accept-Language: de 55.78 + ... """) 55.79 + HTTP/1.1 200 OK 55.80 + ... 55.81 + <html> 55.82 + <body> 55.83 + <!-- fivetest is a Zope 3 style i18n domain, default is a PTS domain --> 55.84 + <p>Dies ist eine Nachricht</p> 55.85 + <p>Diesen Katalog neu einlesen</p> 55.86 + </body> 55.87 + </html> 55.88 + 55.89 +Both systems should also honour Localizer-specific ways of determining 55.90 +the language, for example the ``pts_language`` cookie... 55.91 + 55.92 + >>> print http(r""" 55.93 + ... GET /test_folder_1_/ftf/pts_test_languages.html HTTP/1.1 55.94 + ... Accept-Language: de 55.95 + ... Cookie: pts_language=en 55.96 + ... """) 55.97 + HTTP/1.1 200 OK 55.98 + ... 55.99 + <html> 55.100 + <body> 55.101 + <!-- fivetest is a Zope 3 style i18n domain, default is a PTS domain --> 55.102 + <p>This is a message</p> 55.103 + <p>Reload this catalog</p> 55.104 + </body> 55.105 + </html> 55.106 + 55.107 +... and the ``language`` form field... 55.108 + 55.109 + >>> print http(r""" 55.110 + ... GET /test_folder_1_/ftf/pts_test_languages.html?language=en HTTP/1.1 55.111 + ... Accept-Language: de 55.112 + ... """) 55.113 + HTTP/1.1 200 OK 55.114 + ... 55.115 + <html> 55.116 + <body> 55.117 + <!-- fivetest is a Zope 3 style i18n domain, default is a PTS domain --> 55.118 + <p>This is a message</p> 55.119 + <p>Reload this catalog</p> 55.120 + </body> 55.121 + </html> 55.122 + 55.123 +... and both the ``pts_language`` cookie and the ``language`` form field: 55.124 + 55.125 + >>> print http(r""" 55.126 + ... GET /test_folder_1_/ftf/pts_test_languages.html?language=de HTTP/1.1 55.127 + ... Accept-Language: en 55.128 + ... Cookie: pts_language=fr 55.129 + ... """) 55.130 + HTTP/1.1 200 OK 55.131 + ... 55.132 + <html> 55.133 + <body> 55.134 + <!-- fivetest is a Zope 3 style i18n domain, default is a PTS domain --> 55.135 + <p>Dies ist eine Nachricht</p> 55.136 + <p>Diesen Katalog neu einlesen</p> 55.137 + </body> 55.138 + </html>
56.1 new file mode 100644 56.2 --- /dev/null 56.3 +++ b/browser/tests/resource.txt 56.4 @@ -0,0 +1,116 @@ 56.5 +Testing resources 56.6 +================= 56.7 + 56.8 +Set up the test fixtures: 56.9 + 56.10 + >>> import Products.Five.browser.tests 56.11 + >>> from Products.Five import zcml 56.12 + >>> zcml.load_config("configure.zcml", Products.Five) 56.13 + >>> zcml.load_config('resource.zcml', package=Products.Five.browser.tests) 56.14 + 56.15 + >>> from Products.Five.tests.testing import manage_addFiveTraversableFolder 56.16 + >>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid') 56.17 + 56.18 + >>> import os, glob 56.19 + >>> _prefix = os.path.dirname(Products.Five.browser.tests.__file__) 56.20 + >>> dir_resource_names = [os.path.basename(r) for r in ( 56.21 + ... glob.glob('%s/*.png' % _prefix) + 56.22 + ... glob.glob('%s/*.pt' % _prefix) + 56.23 + ... glob.glob('%s/[a-z]*.py' % _prefix) + 56.24 + ... glob.glob('%s/*.css' % _prefix))] 56.25 + 56.26 + 56.27 +Resource types 56.28 +-------------- 56.29 + 56.30 + >>> from Products.Five.browser.resource import Resource, PageTemplateResource 56.31 + 56.32 +Template resource 56.33 +~~~~~~~~~~~~~~~~~ 56.34 + 56.35 + >>> resource = self.folder.unrestrictedTraverse('testoid/++resource++cockatiel.html') 56.36 + >>> isinstance(resource, Resource) 56.37 + True 56.38 + >>> resource() 56.39 + 'http://nohost/test_folder_1_/testoid/++resource++cockatiel.html' 56.40 + 56.41 +File resource 56.42 +~~~~~~~~~~~~~ 56.43 + 56.44 + >>> resource = self.folder.unrestrictedTraverse('testoid/++resource++style.css') 56.45 + >>> isinstance(resource, Resource) 56.46 + True 56.47 + >>> resource() 56.48 + 'http://nohost/test_folder_1_/testoid/++resource++style.css' 56.49 + 56.50 +Image resource 56.51 +~~~~~~~~~~~~~~ 56.52 + 56.53 + >>> resource = self.folder.unrestrictedTraverse('testoid/++resource++pattern.png') 56.54 + >>> isinstance(resource, Resource) 56.55 + True 56.56 + >>> resource() 56.57 + 'http://nohost/test_folder_1_/testoid/++resource++pattern.png' 56.58 + 56.59 +Resource directory 56.60 +~~~~~~~~~~~~~~~~~~ 56.61 + 56.62 + >>> base = 'testoid/++resource++fivetest_resources/%s' 56.63 + >>> base_url = 'http://nohost/test_folder_1_/' + base 56.64 + >>> abs_url = self.folder.unrestrictedTraverse(base % '')() 56.65 + >>> abs_url + '/' == base_url % '' 56.66 + True 56.67 + 56.68 +PageTemplateResource's __call__ renders the template 56.69 + 56.70 + >>> for r in dir_resource_names: 56.71 + ... resource = self.folder.unrestrictedTraverse(base % r) 56.72 + ... self.assert_(isinstance(resource, Resource)) 56.73 + ... if not isinstance(resource, PageTemplateResource): 56.74 + ... self.assertEquals(resource(), base_url % r) 56.75 + 56.76 + 56.77 +Security 56.78 +-------- 56.79 + 56.80 + >>> from Products.Five.tests.testing.restricted import checkRestricted 56.81 + >>> from Products.Five.tests.testing.restricted import checkUnauthorized 56.82 + 56.83 + >>> resource_names = ['cockatiel.html', 'style.css', 'pattern.png'] 56.84 + 56.85 +We should get Unauthorized as long as we're unauthenticated: 56.86 + 56.87 + >>> for resource in resource_names: 56.88 + ... checkUnauthorized( 56.89 + ... self.folder, 56.90 + ... 'context.restrictedTraverse("testoid/++resource++%s")()' % resource) 56.91 + 56.92 + >>> base = 'testoid/++resource++fivetest_resources/%s' 56.93 + >>> for resource in dir_resource_names: 56.94 + ... path = base % resource 56.95 + ... checkUnauthorized(self.folder, 'context.restrictedTraverse("%s")' % path) 56.96 + 56.97 +Now let's create a manager user account and log in: 56.98 + 56.99 + >>> uf = self.folder.acl_users 56.100 + >>> uf._doAddUser('manager', 'r00t', ['Manager'], []) 56.101 + >>> self.login('manager') 56.102 + 56.103 +We can now view them all: 56.104 + 56.105 + >>> for resource in resource_names: 56.106 + ... checkRestricted( 56.107 + ... self.folder, 56.108 + ... 'context.restrictedTraverse("testoid/++resource++%s")()' % resource) 56.109 + 56.110 + >>> base = 'testoid/++resource++fivetest_resources/%s' 56.111 + >>> for resource in dir_resource_names: 56.112 + ... path = base % resource 56.113 + ... checkRestricted(self.folder, 'context.restrictedTraverse("%s")' % path) 56.114 + 56.115 + 56.116 +Clean up 56.117 +-------- 56.118 + 56.119 + >>> from zope.app.tests.placelesssetup import tearDown 56.120 + >>> tearDown()
57.1 new file mode 100644 57.2 --- /dev/null 57.3 +++ b/browser/tests/resource.zcml 57.4 @@ -0,0 +1,29 @@ 57.5 +<configure xmlns="http://namespaces.zope.org/zope" 57.6 + xmlns:browser="http://namespaces.zope.org/browser"> 57.7 + 57.8 + <!-- a couple simple resources --> 57.9 + <browser:resource 57.10 + template="cockatiel.pt" 57.11 + name="cockatiel.html" 57.12 + permission="zope2.ViewManagementScreens" 57.13 + /> 57.14 + 57.15 + <browser:resource 57.16 + file="style.css" 57.17 + name="style.css" 57.18 + permission="zope2.ViewManagementScreens" 57.19 + /> 57.20 + 57.21 + <browser:resource 57.22 + image="pattern.png" 57.23 + name="pattern.png" 57.24 + permission="zope2.ViewManagementScreens" 57.25 + /> 57.26 + 57.27 + <browser:resourceDirectory 57.28 + name="fivetest_resources" 57.29 + directory="." 57.30 + permission="zope2.ViewManagementScreens" 57.31 + /> 57.32 + 57.33 +</configure> 57.34 \ No newline at end of file
58.1 new file mode 100644 58.2 --- /dev/null 58.3 +++ b/browser/tests/resource_ftest.txt 58.4 @@ -0,0 +1,73 @@ 58.5 +Functional Resource Test 58.6 +======================== 58.7 + 58.8 +Set up the test fixtures: 58.9 + 58.10 + >>> import Products.Five.browser.tests 58.11 + >>> from Products.Five import zcml 58.12 + >>> zcml.load_config("configure.zcml", Products.Five) 58.13 + >>> zcml.load_config('resource.zcml', package=Products.Five.browser.tests) 58.14 + 58.15 + >>> from Products.Five.tests.testing import manage_addFiveTraversableFolder 58.16 + >>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid') 58.17 + 58.18 + >>> import os, glob 58.19 + >>> _prefix = os.path.dirname(Products.Five.browser.tests.__file__) 58.20 + >>> dir_resource_names = [os.path.basename(r) for r in ( 58.21 + ... glob.glob('%s/*.png' % _prefix) + 58.22 + ... glob.glob('%s/*.pt' % _prefix) + 58.23 + ... glob.glob('%s/[a-z]*.py' % _prefix) + 58.24 + ... glob.glob('%s/*.css' % _prefix))] 58.25 + 58.26 + >>> uf = self.folder.acl_users 58.27 + >>> uf._doAddUser('manager', 'r00t', ['Manager'], []) 58.28 + 58.29 + 58.30 +Image resource 58.31 +~~~~~~~~~~~~~~ 58.32 + 58.33 + >>> print http(r''' 58.34 + ... GET /test_folder_1_/testoid/++resource++pattern.png HTTP/1.1 58.35 + ... Authorization: Basic manager:r00t 58.36 + ... ''') 58.37 + HTTP/1.1 200 OK 58.38 + ... 58.39 + 58.40 +File resource 58.41 +~~~~~~~~~~~~~ 58.42 + 58.43 + >>> print http(r''' 58.44 + ... GET /test_folder_1_/testoid/++resource++style.css HTTP/1.1 58.45 + ... Authorization: Basic manager:r00t 58.46 + ... ''') 58.47 + HTTP/1.1 200 OK 58.48 + ... 58.49 + 58.50 +Template resource 58.51 +~~~~~~~~~~~~~~~~~ 58.52 + 58.53 + >>> print http(r''' 58.54 + ... GET /test_folder_1_/testoid/++resource++cockatiel.html HTTP/1.1 58.55 + ... Authorization: Basic manager:r00t 58.56 + ... ''') 58.57 + HTTP/1.1 200 OK 58.58 + ... 58.59 + 58.60 +Resource directory 58.61 +~~~~~~~~~~~~~~~~~~ 58.62 + 58.63 +Page templates aren't guaranteed to render, so exclude them from the test: 58.64 + 58.65 + >>> base_url = '/test_folder_1_/testoid/++resource++fivetest_resources/%s' 58.66 + >>> for r in dir_resource_names: 58.67 + ... if r.endswith('.pt'): 58.68 + ... continue 58.69 + ... response = self.publish(base_url % r, basic='manager:r00t') 58.70 + ... self.assertEquals(200, response.getStatus()) 58.71 + 58.72 + 58.73 +Clean up 58.74 +-------- 58.75 + 58.76 + >>> from zope.app.tests.placelesssetup import tearDown 58.77 + >>> tearDown()
59.1 new file mode 100644 59.2 --- /dev/null 59.3 +++ b/browser/tests/seagull.pt 59.4 @@ -0,0 +1,1 @@ 59.5 +<html metal:use-macro="context/@@bird.html/birdmacro"><metal:block fill-slot="color">gray</metal:block></html>
60.1 new file mode 100644 60.2 --- /dev/null 60.3 +++ b/browser/tests/security.pt 60.4 @@ -0,0 +1,5 @@ 60.5 +<div tal:define="comment string:Testing unrestricted code" 60.6 + tal:content="python:None.__class__.__name__" /> 60.7 +<div tal:define="comment string:Testing unrestricted modules access; 60.8 + smtpd nocall:modules/smtpd" 60.9 + tal:content="python:smtpd.__name__" />
61.1 new file mode 100644 61.2 --- /dev/null 61.3 +++ b/browser/tests/skin.txt 61.4 @@ -0,0 +1,54 @@ 61.5 +Test layer and skin support 61.6 +=========================== 61.7 + 61.8 +Let's register a test layer and test skin: 61.9 + 61.10 + >>> import Products.Five.browser.tests 61.11 + >>> from Products.Five import zcml 61.12 + >>> zcml.load_config("configure.zcml", Products.Five) 61.13 + >>> zcml.load_config("skin.zcml", package=Products.Five.browser.tests) 61.14 + 61.15 +Let's add a test object that we'll access the test page from: 61.16 + 61.17 + >>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent 61.18 + >>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid') 61.19 + 61.20 +The view was registered on a different layer than 'default', that's 61.21 +why we can't access it straight away: 61.22 + 61.23 + >>> print http(r""" 61.24 + ... GET /test_folder_1_/testoid/eagle.html HTTP/1.1 61.25 + ... """) 61.26 + HTTP/1.1 404 Not Found 61.27 + ... 61.28 + 61.29 +It works when we explicitly use the skin that includes that layer: 61.30 + 61.31 + >>> print http(r""" 61.32 + ... GET /test_folder_1_/testoid/++skin++TestSkin/eagle.html HTTP/1.1 61.33 + ... """) 61.34 + HTTP/1.1 200 OK 61.35 + ... 61.36 + The eagle has landed 61.37 + 61.38 +Or when we make that skin the default skin: 61.39 + 61.40 + >>> zcml.load_string(''' 61.41 + ... <browser:defaultSkin 61.42 + ... xmlns:browser="http://namespaces.zope.org/browser" 61.43 + ... name="TestSkin" /> 61.44 + ... ''') 61.45 + 61.46 + >>> print http(r""" 61.47 + ... GET /test_folder_1_/testoid/eagle.html HTTP/1.1 61.48 + ... """) 61.49 + HTTP/1.1 200 OK 61.50 + ... 61.51 + The eagle has landed 61.52 + 61.53 + 61.54 +Clean up 61.55 +-------- 61.56 + 61.57 + >>> from zope.app.tests.placelesssetup import tearDown 61.58 + >>> tearDown()
62.1 new file mode 100644 62.2 --- /dev/null 62.3 +++ b/browser/tests/skin.zcml 62.4 @@ -0,0 +1,24 @@ 62.5 +<configure xmlns="http://namespaces.zope.org/zope" 62.6 + xmlns:meta="http://namespaces.zope.org/meta" 62.7 + xmlns:browser="http://namespaces.zope.org/browser"> 62.8 + 62.9 + <!-- make the zope2.Public permission work --> 62.10 + <meta:redefinePermission from="zope2.Public" to="zope.Public" /> 62.11 + 62.12 + <browser:layer name="test" /> 62.13 + 62.14 + <browser:skin 62.15 + name="TestSkin" 62.16 + layers="test default" 62.17 + /> 62.18 + 62.19 + <browser:page 62.20 + for="Products.Five.tests.testing.simplecontent.ISimpleContent" 62.21 + class=".pages.SimpleView" 62.22 + attribute="eagle" 62.23 + name="eagle.html" 62.24 + permission="zope2.Public" 62.25 + layer="test" 62.26 + /> 62.27 + 62.28 +</configure>
63.1 new file mode 100644 63.2 --- /dev/null 63.3 +++ b/browser/tests/style.css 63.4 @@ -0,0 +1,1 @@ 63.5 +a { text-decoration: none; }
64.1 new file mode 100644 64.2 --- /dev/null 64.3 +++ b/browser/tests/tales_traversal.pt 64.4 @@ -0,0 +1,2 @@ 64.5 +<p tal:content="context/non_existent_thingie/fubared|context/getId">dunno</p> 64.6 +<p tal:content="context/test_folder_1_/test_folder_1_/getId">dunno</p>
65.1 new file mode 100644 65.2 --- /dev/null 65.3 +++ b/browser/tests/template_variables.pt 65.4 @@ -0,0 +1,23 @@ 65.5 +View is a view: <tal:block 65.6 + content="python:hasattr(view,'context') and hasattr(view, 'request')" /> 65.7 +Context is testoid: <tal:block content="python:context.id == 'testoid'" /> 65.8 +Contaxt.aq_parent is test_folder_1_: <tal:block 65.9 + content="python:context.aq_parent.id =='test_folder_1_'" /> 65.10 +Container is context: <tal:block content="python:container is context" /> 65.11 +Here is context: <tal:block content="python:here is context"/> 65.12 +Nothing is None: <tal:block content="python:nothing is None"/> 65.13 +Default works: <tal:block replace="non_existent_var|default" />True 65.14 +Root is the application: <tal:block 65.15 + replace="python:repr(root).find('Application') != -1" /> 65.16 +Template is a template: <tal:block 65.17 + replace="python:repr(template.aq_base).startswith('<ZopeTwoPageTemplateFile')" /> 65.18 +Traverse_subpath exists and is empty: <tal:block 65.19 + replace="python:traverse_subpath == []" /> 65.20 +Request is a request: <tal:block 65.21 + replace="python:getattr(request, 'RESPONSE', None) is not None" /> 65.22 +User is manager: <tal:block replace="python:str(user) == 'manager'" /> 65.23 +Options exist: <tal:block replace="python:options is not None" /> 65.24 +Attrs exist: <tal:block replace="python:attrs is not None" /> 65.25 +Repeat exists: <tal:block replace="python:repeat is not None" /> 65.26 +Loop exists: <tal:block replace="python:loop is not None" /> 65.27 +Modules exists: <tal:block replace="python:modules is not None" /> 65.28 \ No newline at end of file
66.1 new file mode 100644 66.2 --- /dev/null 66.3 +++ b/browser/tests/test_absoluteurl.py 66.4 @@ -0,0 +1,98 @@ 66.5 +############################################################################## 66.6 +# 66.7 +# Copyright (c) 2004, 2005 Zope Corporation and Contributors. 66.8 +# All Rights Reserved. 66.9 +# 66.10 +# This software is subject to the provisions of the Zope Public License, 66.11 +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 66.12 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 66.13 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 66.14 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 66.15 +# FOR A PARTICULAR PURPOSE. 66.16 +# 66.17 +############################################################################## 66.18 +"""Test AbsoluteURL 66.19 + 66.20 +$Id: test_absoluteurl.py 17853 2005-09-25 13:34:25Z tseaver $ 66.21 +""" 66.22 +import os, sys 66.23 +if __name__ == '__main__': 66.24 + execfile(os.path.join(sys.path[0], 'framework.py')) 66.25 + 66.26 +def test_absoluteurl(): 66.27 + """This tests the absolute url view (IAbsoluteURL or @@absolute_url), 66.28 + in particular the breadcrumb functionality. 66.29 + 66.30 + First we make some preparations: 66.31 + 66.32 + >>> import Products.Five 66.33 + >>> from Products.Five import zcml 66.34 + >>> zcml.load_config("configure.zcml", Products.Five) 66.35 + 66.36 + >>> from Products.Five.tests.testing import manage_addFiveTraversableFolder 66.37 + >>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid') 66.38 + 66.39 + A simple traversal will yield us the @@absolute_url view: 66.40 + 66.41 + >>> view = self.folder.unrestrictedTraverse('testoid/@@absolute_url') 66.42 + >>> view() 66.43 + 'http://nohost/test_folder_1_/testoid' 66.44 + 66.45 + IAbsoluteURL also defines a breadcrumbs() method that returns a 66.46 + simple Python structure: 66.47 + 66.48 + >>> for crumb in view.breadcrumbs(): 66.49 + ... info = crumb.items() 66.50 + ... info.sort() 66.51 + ... info 66.52 + [('name', ''), ('url', 'http://nohost')] 66.53 + [('name', 'test_folder_1_'), ('url', 'http://nohost/test_folder_1_')] 66.54 + [('name', 'testoid'), ('url', 'http://nohost/test_folder_1_/testoid')] 66.55 + 66.56 + This test assures and demonstrates that the absolute url stops 66.57 + traversing through an object's parents when it has reached the 66.58 + root object. In Zope 3 this is marked with the IContainmentRoot 66.59 + interface: 66.60 + 66.61 + >>> from zope.interface import directlyProvides, providedBy 66.62 + >>> from zope.app.traversing.interfaces import IContainmentRoot 66.63 + >>> directlyProvides(self.folder, IContainmentRoot) 66.64 + 66.65 + >>> for crumb in view.breadcrumbs(): 66.66 + ... info = crumb.items() 66.67 + ... info.sort() 66.68 + ... info 66.69 + [('name', 'test_folder_1_'), ('url', 'http://nohost/test_folder_1_')] 66.70 + [('name', 'testoid'), ('url', 'http://nohost/test_folder_1_/testoid')] 66.71 + 66.72 + >>> directlyProvides(self.folder, 66.73 + ... providedBy(self.folder) - IContainmentRoot) 66.74 + 66.75 + The absolute url view is obviously not affected by virtual hosting: 66.76 + 66.77 + >>> request = self.app.REQUEST 66.78 + >>> request['PARENTS'] = [self.folder.test_folder_1_] 66.79 + >>> url = request.setServerURL( 66.80 + ... protocol='http', hostname='foo.bar.com', port='80') 66.81 + >>> request.setVirtualRoot('') 66.82 + 66.83 + >>> for crumb in view.breadcrumbs(): 66.84 + ... info = crumb.items() 66.85 + ... info.sort() 66.86 + ... info 66.87 + [('name', 'test_folder_1_'), ('url', 'http://foo.bar.com')] 66.88 + [('name', 'testoid'), ('url', 'http://foo.bar.com/testoid')] 66.89 + 66.90 + 66.91 + Clean up: 66.92 + 66.93 + >>> from zope.app.tests.placelesssetup import tearDown 66.94 + >>> tearDown() 66.95 + """ 66.96 + 66.97 +def test_suite(): 66.98 + from Testing.ZopeTestCase import ZopeDocTestSuite 66.99 + return ZopeDocTestSuite() 66.100 + 66.101 +if __name__ == '__main__': 66.102 + framework()
67.1 new file mode 100644 67.2 --- /dev/null 67.3 +++ b/browser/tests/test_adding.py 67.4 @@ -0,0 +1,28 @@ 67.5 +############################################################################## 67.6 +# 67.7 +# Copyright (c) 2004, 2005 Zope Corporation and Contributors. 67.8 +# All Rights Reserved. 67.9 +# 67.10 +# This software is subject to the provisions of the Zope Public License, 67.11 +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 67.12 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 67.13 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 67.14 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 67.15 +# FOR A PARTICULAR PURPOSE. 67.16 +# 67.17 +############################################################################## 67.18 +"""Test adding views 67.19 + 67.20 +$Id: test_adding.py 19157 2005-10-29 10:27:28Z philikon $ 67.21 +""" 67.22 +import os, sys 67.23 +if __name__ == '__main__': 67.24 + execfile(os.path.join(sys.path[0], 'framework.py')) 67.25 + 67.26 +def test_suite(): 67.27 + from Testing.ZopeTestCase import ZopeDocFileSuite 67.28 + return ZopeDocFileSuite('adding.txt', 67.29 + package="Products.Five.browser.tests") 67.30 + 67.31 +if __name__ == '__main__': 67.32 + framework()
68.1 new file mode 100644 68.2 --- /dev/null 68.3 +++ b/browser/tests/test_defaultview.py 68.4 @@ -0,0 +1,91 @@ 68.5 +############################################################################## 68.6 +# 68.7 +# Copyright (c) 2004, 2005 Zope Corporation and Contributors. 68.8 +# All Rights Reserved. 68.9 +# 68.10 +# This software is subject to the provisions of the Zope Public License, 68.11 +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 68.12 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 68.13 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 68.14 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 68.15 +# FOR A PARTICULAR PURPOSE. 68.16 +# 68.17 +############################################################################## 68.18 +"""Test Default View functionality 68.19 + 68.20 +$Id: test_defaultview.py 17853 2005-09-25 13:34:25Z tseaver $ 68.21 +""" 68.22 +import os, sys 68.23 +if __name__ == '__main__': 68.24 + execfile(os.path.join(sys.path[0], 'framework.py')) 68.25 + 68.26 +def test_default_view(): 68.27 + """ 68.28 + Test default view functionality 68.29 + 68.30 + Let's register a couple of default views and make our stub classes 68.31 + default viewable: 68.32 + 68.33 + >>> import Products.Five.browser.tests 68.34 + >>> from Products.Five import zcml 68.35 + >>> zcml.load_config("configure.zcml", Products.Five) 68.36 + >>> zcml.load_config('defaultview.zcml', Products.Five.browser.tests) 68.37 + 68.38 + Now let's add a couple of stub objects: 68.39 + 68.40 + >>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent 68.41 + >>> from Products.Five.tests.testing.simplecontent import manage_addCallableSimpleContent 68.42 + >>> from Products.Five.tests.testing.simplecontent import manage_addIndexSimpleContent 68.43 + 68.44 + >>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid') 68.45 + >>> manage_addCallableSimpleContent(self.folder, 'testcall', 'TestCall') 68.46 + >>> manage_addIndexSimpleContent(self.folder, 'testindex', 'TestIndex') 68.47 + 68.48 + As a last act of preparation, we create a manager login: 68.49 + 68.50 + >>> uf = self.folder.acl_users 68.51 + >>> uf._doAddUser('manager', 'r00t', ['Manager'], []) 68.52 + 68.53 + Test a simple default view: 68.54 + 68.55 + >>> print http(r''' 68.56 + ... GET /test_folder_1_/testoid HTTP/1.1 68.57 + ... Authorization: Basic manager:r00t 68.58 + ... ''') 68.59 + HTTP/1.1 200 OK 68.60 + ... 68.61 + The eagle has landed 68.62 + 68.63 + This tests whether an existing ``index_html`` method is still 68.64 + supported and called: 68.65 + 68.66 + >>> print http(r''' 68.67 + ... GET /test_folder_1_/testindex HTTP/1.1 68.68 + ... ''') 68.69 + HTTP/1.1 200 OK 68.70 + ... 68.71 + Default index_html called 68.72 + 68.73 + Disabled __call__ overriding for now. Causese more trouble than it 68.74 + fixes. Thus, no test here: 68.75 + 68.76 + #>>> print http(r''' 68.77 + #... GET /test_folder_1_/testcall HTTP/1.1 68.78 + #... ''') 68.79 + #HTTP/1.1 200 OK 68.80 + #... 68.81 + #Default __call__ called 68.82 + 68.83 + 68.84 + Clean up: 68.85 + 68.86 + >>> from zope.app.tests.placelesssetup import tearDown 68.87 + >>> tearDown() 68.88 + """ 68.89 + 68.90 +def test_suite(): 68.91 + from Testing.ZopeTestCase import FunctionalDocTestSuite 68.92 + return FunctionalDocTestSuite() 68.93 + 68.94 +if __name__ == '__main__': 68.95 + framework()
69.1 new file mode 100644 69.2 --- /dev/null 69.3 +++ b/browser/tests/test_i18n.py 69.4 @@ -0,0 +1,93 @@ 69.5 +############################################################################## 69.6 +# 69.7 +# Copyright (c) 2004, 2005 Zope Corporation and Contributors. 69.8 +# All Rights Reserved. 69.9 +# 69.10 +# This software is subject to the provisions of the Zope Public License, 69.11 +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 69.12 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 69.13 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 69.14 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 69.15 +# FOR A PARTICULAR PURPOSE. 69.16 +# 69.17 +############################################################################## 69.18 +"""Unit tests for the i18n framework 69.19 + 69.20 +$Id: test_i18n.py 18829 2005-10-21 17:22:05Z yuppie $ 69.21 +""" 69.22 +import os, sys 69.23 +if __name__ == '__main__': 69.24 + execfile(os.path.join(sys.path[0], 'framework.py')) 69.25 + 69.26 +def test_zpt_i18n(): 69.27 + """ 69.28 + Test i18n functionality in ZPTs 69.29 + 69.30 + >>> configure_zcml = ''' 69.31 + ... <configure 69.32 + ... xmlns="http://namespaces.zope.org/zope" 69.33 + ... xmlns:browser="http://namespaces.zope.org/browser" 69.34 + ... xmlns:i18n="http://namespaces.zope.org/i18n"> 69.35 + ... <configure package="Products.Five.tests"> 69.36 + ... <i18n:registerTranslations directory="locales" /> 69.37 + ... </configure> 69.38 + ... <configure package="Products.Five.browser.tests"> 69.39 + ... <browser:page 69.40 + ... for="OFS.interfaces.IFolder" 69.41 + ... template="i18n.pt" 69.42 + ... name="i18n.html" 69.43 + ... permission="zope2.View" 69.44 + ... /> 69.45 + ... </configure> 69.46 + ... </configure>''' 69.47 + 69.48 + >>> import Products.Five 69.49 + >>> from Products.Five import zcml 69.50 + >>> zcml.load_config("configure.zcml", Products.Five) 69.51 + >>> zcml.load_string(configure_zcml) 69.52 + 69.53 + In order to be able to traverse to the PageTemplate view, we need 69.54 + a traversable object: 69.55 + 69.56 + >>> from Products.Five.tests.testing import manage_addFiveTraversableFolder 69.57 + >>> manage_addFiveTraversableFolder(self.folder, 'testoid', 'Testoid') 69.58 + 69.59 + We tell Zope to translate the messages by passing the 69.60 + ``Accept-Language`` header which is processed by the 69.61 + ``IUserPreferredLangauges`` adapter: 69.62 + 69.63 + >>> print http(r''' 69.64 + ... GET /test_folder_1_/testoid/@@i18n.html HTTP/1.1 69.65 + ... Accept-Language: de 69.66 + ... ''') 69.67 + HTTP/1.1 200 OK 69.68 + ... 69.69 + <html> 69.70 + <body> 69.71 + <p>Dies ist eine Nachricht</p> 69.72 + <p>Dies ist eine explizite Nachricht</p> 69.73 + <p>Dies sind 4 Nachrichten</p> 69.74 + <p>Dies sind 4 explizite Nachrichten</p> 69.75 + <table summary="Dies ist ein Attribut"> 69.76 + </table> 69.77 + <table summary="Explizite Zusammenfassung" 69.78 + title="Expliziter Titel"> 69.79 + </table> 69.80 + </body> 69.81 + </html> 69.82 + ... 69.83 + 69.84 + 69.85 + Clean up: 69.86 + 69.87 + >>> from zope.app.tests.placelesssetup import tearDown 69.88 + >>> tearDown() 69.89 + """ 69.90 + 69.91 +def test_suite(): 69.92 + from Testing.ZopeTestCase import FunctionalDocTestSuite 69.93 + from zope.testing.doctest import ELLIPSIS 69.94 + return FunctionalDocTestSuite(optionflags=ELLIPSIS) 69.95 + 69.96 +if __name__ == '__main__': 69.97 + framework()
70.1 new file mode 100644 70.2 --- /dev/null 70.3 +++ b/browser/tests/test_menu.py 70.4 @@ -0,0 +1,143 @@ 70.5 +############################################################################## 70.6 +# 70.7 +# Copyright (c) 2004, 2005 Zope Corporation and Contributors. 70.8 +# All Rights Reserved. 70.9 +# 70.10 +# This software is subject to the provisions of the Zope Public License, 70.11 +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 70.12 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 70.13 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 70.14 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 70.15 +# FOR A PARTICULAR PURPOSE. 70.16 +# 70.17 +############################################################################## 70.18 +"""Test browser menus 70.19 + 70.20 +$Id: test_menu.py 14595 2005-07-12 21:26:12Z philikon $ 70.21 +""" 70.22 +import os, sys 70.23 +if __name__ == '__main__': 70.24 + execfile(os.path.join(sys.path[0], 'framework.py')) 70.25 + 70.26 +def test_menu(): 70.27 + """ 70.28 + Test menus 70.29 + 70.30 + Before we can start we need to set up a few things. For menu 70.31 + configuration, we have to start a new interaction: 70.32 + 70.33 + >>> import Products.Five.browser.tests 70.34 + >>> from Products.Five import zcml 70.35 + >>> zcml.load_config("configure.zcml", Products.Five) 70.36 + >>> zcml.load_config('menu.zcml', package=Products.Five.browser.tests) 70.37 + 70.38 + >>> from Products.Five.security import newInteraction 70.39 + >>> newInteraction() 70.40 + 70.41 + Now for some actual testing... Let's look up the menu we registered: 70.42 + 70.43 + >>> from Products.Five.traversable import FakeRequest 70.44 + >>> from zope.app.publisher.browser.globalbrowsermenuservice import \\ 70.45 + ... globalBrowserMenuService 70.46 + 70.47 + >>> request = FakeRequest() 70.48 + >>> menu = globalBrowserMenuService.getMenu( 70.49 + ... 'testmenu', self.folder, request) 70.50 + 70.51 + It should have 70.52 + 70.53 + >>> len(menu) 70.54 + 4 70.55 + 70.56 + Sort menu items by title so we get a stable testable result: 70.57 + 70.58 + >>> menu.sort(lambda x, y: cmp(x['title'], y['title'])) 70.59 + >>> from pprint import pprint 70.60 + >>> pprint(menu) 70.61 + [{'action': '@@cockatiel_menu_public.html', 70.62 + 'description': '', 70.63 + 'extra': None, 70.64 + 'selected': '', 70.65 + 'title': u'Page in a menu (public)'}, 70.66 + {'action': u'seagull.html', 70.67 + 'description': u'This is a test menu item', 70.68 + 'extra': None, 70.69 + 'selected': '', 70.70 + 'title': u'Test Menu Item'}, 70.71 + {'action': u'parakeet.html', 70.72 + 'description': u'This is a test menu item', 70.73 + 'extra': None, 70.74 + 'selected': '', 70.75 + 'title': u'Test Menu Item 2'}, 70.76 + {'action': u'falcon.html', 70.77 + 'description': u'This is a test menu item', 70.78 + 'extra': None, 70.79 + 'selected': '', 70.80 + 'title': u'Test Menu Item 3'}] 70.81 + 70.82 + Let's create a manager user account and log in. 70.83 + 70.84 + >>> uf = self.folder.acl_users 70.85 + >>> uf._doAddUser('manager', 'r00t', ['Manager'], []) 70.86 + >>> self.login('manager') 70.87 + >>> newInteraction() 70.88 + 70.89 + >>> menu = globalBrowserMenuService.getMenu( 70.90 + ... 'testmenu', self.folder, request) 70.91 + 70.92 + We should get the protected menu items now: 70.93 + 70.94 + >>> len(menu) 70.95 + 7 70.96 + 70.97 + >>> menu.sort(lambda x, y: cmp(x['title'], y['title'])) 70.98 + >>> pprint(menu) 70.99 + [{'action': '@@cockatiel_menu_protected.html', 70.100 + 'description': '', 70.101 + 'extra': None, 70.102 + 'selected': '', 70.103 + 'title': u'Page in a menu (protected)'}, 70.104 + {'action': '@@cockatiel_menu_public.html', 70.105 + 'description': '', 70.106 + 'extra': None, 70.107 + 'selected': '', 70.108 + 'title': u'Page in a menu (public)'}, 70.109 + {'action': u'seagull.html', 70.110 + 'description': u'This is a protected test menu item', 70.111 + 'extra': None, 70.112 + 'selected': '', 70.113 + 'title': u'Protected Test Menu Item'}, 70.114 + {'action': u'falcon.html', 70.115 + 'description': u'This is a protected test menu item', 70.116 + 'extra': None, 70.117 + 'selected': '', 70.118 + 'title': u'Protected Test Menu Item 2'}, 70.119 + {'action': u'seagull.html', 70.120 + 'description': u'This is a test menu item', 70.121 + 'extra': None, 70.122 + 'selected': '', 70.123 + 'title': u'Test Menu Item'}, 70.124 + {'action': u'parakeet.html', 70.125 + 'description': u'This is a test menu item', 70.126 + 'extra': None, 70.127 + 'selected': '', 70.128 + 'title': u'Test Menu Item 2'}, 70.129 + {'action': u'falcon.html', 70.130 + 'description': u'This is a test menu item', 70.131 + 'extra': None, 70.132 + 'selected': '', 70.133 + 'title': u'Test Menu Item 3'}] 70.134 + 70.135 + 70.136 + Clean up: 70.137 + 70.138 + >>> from zope.app.tests.placelesssetup import tearDown 70.139 + >>> tearDown() 70.140 + """ 70.141 + 70.142 +def test_suite(): 70.143 + from Testing.ZopeTestCase import ZopeDocTestSuite 70.144 + return ZopeDocTestSuite() 70.145 + 70.146 +if __name__ == '__main__': 70.147 + framework()
71.1 new file mode 100644 71.2 --- /dev/null 71.3 +++ b/browser/tests/test_pages.py 71.4 @@ -0,0 +1,78 @@ 71.5 +############################################################################## 71.6 +# 71.7 +# Copyright (c) 2004, 2005 Zope Corporation and Contributors. 71.8 +# All Rights Reserved. 71.9 +# 71.10 +# This software is subject to the provisions of the Zope Public License, 71.11 +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 71.12 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 71.13 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 71.14 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 71.15 +# FOR A PARTICULAR PURPOSE. 71.16 +# 71.17 +############################################################################## 71.18 +"""Test browser pages 71.19 + 71.20 +$Id: test_pages.py 18860 2005-10-24 12:14:34Z philikon $ 71.21 +""" 71.22 +import os, sys 71.23 +if __name__ == '__main__': 71.24 + execfile(os.path.join(sys.path[0], 'framework.py')) 71.25 + 71.26 +def test_ViewAcquisitionWrapping(): 71.27 + """ 71.28 + >>> import Products.Five.browser.tests 71.29 + >>> from Products.Five import zcml 71.30 + >>> zcml.load_config("configure.zcml", Products.Five) 71.31 + >>> zcml.load_config('pages.zcml', package=Products.Five.browser.tests) 71.32 + 71.33 + >>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent 71.34 + >>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid') 71.35 + >>> uf = self.folder.acl_users 71.36 + >>> uf._doAddUser('manager', 'r00t', ['Manager'], []) 71.37 + >>> self.login('manager') 71.38 + 71.39 + >>> view = self.folder.unrestrictedTraverse('testoid/eagle.txt') 71.40 + >>> view is not None 71.41 + True 71.42 + >>> from Products.Five.browser.tests.pages import SimpleView 71.43 + >>> isinstance(view, SimpleView) 71.44 + True 71.45 + >>> view() 71.46 + 'The eagle has landed' 71.47 + 71.48 + This sucks, but we know it 71.49 + 71.50 + >>> from Acquisition import aq_parent, aq_base 71.51 + >>> aq_parent(view.context) is view 71.52 + True 71.53 + 71.54 + This is the right way to get the context parent 71.55 + 71.56 + >>> view.context.aq_inner.aq_parent is not view 71.57 + True 71.58 + >>> view.context.aq_inner.aq_parent is self.folder 71.59 + True 71.60 + 71.61 + Clean up: 71.62 + 71.63 + >>> from zope.app.tests.placelesssetup import tearDown 71.64 + >>> tearDown() 71.65 + """ 71.66 + 71.67 +def test_suite(): 71.68 + import unittest 71.69 + from Testing.ZopeTestCase import installProduct, ZopeDocTestSuite 71.70 + from Testing.ZopeTestCase import ZopeDocFileSuite 71.71 + from Testing.ZopeTestCase import FunctionalDocFileSuite 71.72 + installProduct('PythonScripts') # for Five.tests.testing.restricted 71.73 + return unittest.TestSuite(( 71.74 + ZopeDocTestSuite(), 71.75 + ZopeDocFileSuite('pages.txt', package='Products.Five.browser.tests'), 71.76 + FunctionalDocFileSuite('pages_ftest.txt', 71.77 + package='Products.Five.browser.tests') 71.78 + )) 71.79 + return suite 71.80 + 71.81 +if __name__ == '__main__': 71.82 + framework()
72.1 new file mode 100644 72.2 --- /dev/null 72.3 +++ b/browser/tests/test_recurse.py 72.4 @@ -0,0 +1,78 @@ 72.5 +############################################################################## 72.6 +# 72.7 +# Copyright (c) 2004, 2005 Zope Corporation and Contributors. 72.8 +# All Rights Reserved. 72.9 +# 72.10 +# This software is subject to the provisions of the Zope Public License, 72.11 +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 72.12 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 72.13 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 72.14 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 72.15 +# FOR A PARTICULAR PURPOSE. 72.16 +# 72.17 +############################################################################## 72.18 +"""Test default view recursion 72.19 + 72.20 +$Id: test_recurse.py 14595 2005-07-12 21:26:12Z philikon $ 72.21 +""" 72.22 +import os, sys 72.23 +if __name__ == '__main__': 72.24 + execfile(os.path.join(sys.path[0], 'framework.py')) 72.25 + 72.26 +def test_recursion(): 72.27 + """ 72.28 + Test recursion 72.29 + 72.30 + >>> from zope.app.tests.placelesssetup import setUp, tearDown 72.31 + >>> setUp() 72.32 + 72.33 + This test makes sure that recursion is avoided for view lookup. 72.34 + First, we need to set up a stub interface... 72.35 + 72.36 + >>> from zope.interface import Interface, implements 72.37 + >>> class IRecurse(Interface): 72.38 + ... pass 72.39 + ... 72.40 + 72.41 + and a class that is callable and has a view method: 72.42 + 72.43 + >>> from OFS.Traversable import Traversable 72.44 + >>> class Recurse(Traversable): 72.45 + ... implements(IRecurse) 72.46 + ... def view(self): 72.47 + ... return self() 72.48 + ... def __call__(self): 72.49 + ... return 'foo' 72.50 + ... 72.51 + 72.52 + Now we make the class default viewable and register a default view 72.53 + name for it: 72.54 + 72.55 + >>> from Products.Five.fiveconfigure import classDefaultViewable 72.56 + >>> classDefaultViewable(Recurse) 72.57 + 72.58 + >>> from zope.app import zapi 72.59 + >>> from zope.publisher.interfaces.browser import IBrowserRequest 72.60 + >>> pres = zapi.getGlobalService('Presentation') 72.61 + >>> pres.setDefaultViewName(IRecurse, IBrowserRequest, 'view') 72.62 + 72.63 + Here comes the actual test: 72.64 + 72.65 + >>> ob = Recurse() 72.66 + >>> ob.view() 72.67 + 'foo' 72.68 + >>> ob() 72.69 + 'foo' 72.70 + 72.71 + 72.72 + Clean up: 72.73 + 72.74 + >>> tearDown() 72.75 + """ 72.76 + 72.77 +def test_suite(): 72.78 + from Testing.ZopeTestCase import ZopeDocTestSuite 72.79 + return ZopeDocTestSuite() 72.80 + 72.81 +if __name__ == '__main__': 72.82 + framework()
73.1 new file mode 100644 73.2 --- /dev/null 73.3 +++ b/browser/tests/test_resource.py 73.4 @@ -0,0 +1,35 @@ 73.5 +############################################################################## 73.6 +# 73.7 +# Copyright (c) 2004, 2005 Zope Corporation and Contributors. 73.8 +# All Rights Reserved. 73.9 +# 73.10 +# This software is subject to the provisions of the Zope Public License, 73.11 +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 73.12 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 73.13 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 73.14 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 73.15 +# FOR A PARTICULAR PURPOSE. 73.16 +# 73.17 +############################################################################## 73.18 +"""Test browser resources 73.19 + 73.20 +$Id: test_resource.py 17853 2005-09-25 13:34:25Z tseaver $ 73.21 +""" 73.22 +import os, sys 73.23 +if __name__ == '__main__': 73.24 + execfile(os.path.join(sys.path[0], 'framework.py')) 73.25 + 73.26 +def test_suite(): 73.27 + import unittest 73.28 + from Testing.ZopeTestCase import installProduct, ZopeDocFileSuite 73.29 + from Testing.ZopeTestCase import FunctionalDocFileSuite 73.30 + installProduct('PythonScripts') # for Five.tests.testing.restricted 73.31 + return unittest.TestSuite(( 73.32 + ZopeDocFileSuite('resource.txt', 73.33 + package='Products.Five.browser.tests'), 73.34 + FunctionalDocFileSuite('resource_ftest.txt', 73.35 + package='Products.Five.browser.tests'), 73.36 + )) 73.37 + 73.38 +if __name__ == '__main__': 73.39 + framework()
74.1 new file mode 100644 74.2 --- /dev/null 74.3 +++ b/browser/tests/test_skin.py 74.4 @@ -0,0 +1,28 @@ 74.5 +############################################################################## 74.6 +# 74.7 +# Copyright (c) 2005 Zope Corporation and Contributors. 74.8 +# All Rights Reserved. 74.9 +# 74.10 +# This software is subject to the provisions of the Zope Public License, 74.11 +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 74.12 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 74.13 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 74.14 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 74.15 +# FOR A PARTICULAR PURPOSE. 74.16 +# 74.17 +############################################################################## 74.18 +"""Test browser pages 74.19 + 74.20 +$Id: test_skin.py 20072 2005-11-19 09:31:28Z philikon $ 74.21 +""" 74.22 +import os, sys 74.23 +if __name__ == '__main__': 74.24 + execfile(os.path.join(sys.path[0], 'framework.py')) 74.25 + 74.26 +def test_suite(): 74.27 + from Testing.ZopeTestCase import FunctionalDocFileSuite 74.28 + return FunctionalDocFileSuite('skin.txt', 74.29 + package='Products.Five.browser.tests') 74.30 + 74.31 +if __name__ == '__main__': 74.32 + framework()
75.1 new file mode 100644 75.2 --- /dev/null 75.3 +++ b/browser/tests/test_traversable.py 75.4 @@ -0,0 +1,106 @@ 75.5 +############################################################################## 75.6 +# 75.7 +# Copyright (c) 2004, 2005 Zope Corporation and Contributors. 75.8 +# All Rights Reserved. 75.9 +# 75.10 +# This software is subject to the provisions of the Zope Public License, 75.11 +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. 75.12 +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED 75.13 +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 75.14 +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS 75.15 +# FOR A PARTICULAR PURPOSE. 75.16 +# 75.17 +############################################################################## 75.18 +"""Test Five-traversable classes 75.19 + 75.20 +$Id: test_traversable.py 17853 2005-09-25 13:34:25Z tseaver $ 75.21 +""" 75.22 +import os, sys 75.23 +if __name__ == '__main__': 75.24 + execfile(os.path.join(sys.path[0], 'framework.py')) 75.25 + 75.26 +def test_traversable(): 75.27 + """ 75.28 + Test the behaviour of Five-traversable classes. 75.29 + 75.30 + >>> import Products.Five 75.31 + >>> from Products.Five import zcml 75.32 + >>> zcml.load_config("configure.zcml", Products.Five) 75.33 + 75.34 + ``SimpleContent`` is a traversable class by default. Its fallback 75.35 + traverser should raise NotFound when traversal fails. (Note: If 75.36 + we return None in __fallback_traverse__, this test passes but for 75.37 + the wrong reason: None doesn't have a docstring so BaseRequest 75.38 + raises NotFoundError.) 75.39 + 75.40 + >>> from Products.Five.tests.testing.simplecontent import manage_addSimpleContent 75.41 + >>> manage_addSimpleContent(self.folder, 'testoid', 'Testoid') 75.42 + >>> print http(r''' 75.43 + ... GET /test_folder_1_/testoid/doesntexist HTTP/1.1 75.44 + ... ''') 75.45 + HTTP/1.1 404 Not Found 75.46 + ... 75.47 + 75.48 + Now let's take class which already has a __bobo_traverse__ method. 75.49 + Five should correctly use that as a fallback. 75.50 + 75.51 + >>> configure_zcml = ''' 75.52 + ... <configure xmlns="http://namespaces.zope.org/zope" 75.53 + ... xmlns:meta="http://namespaces.zope.org/meta" 75.54 + ... xmlns:browser="http://namespaces.zope.org/browser" 75.55 + ... xmlns:five="http://namespaces.zope.org/five"> 75.56 + ... 75.57 + ... <!-- make the zope2.Public permission work --> 75.58 + ... <meta:redefinePermission from="zope2.Public" to="zope.Public" /> 75.59 + ... 75.60 + ... <five:traversable 75.61 + ... class="Products.Five.tests.testing.fancycontent.FancyContent" 75.62 + ... /> 75.63 + ... 75.64 + ... <browser:page 75.65 + ... for="Products.Five.tests.testing.fancycontent.IFancyContent" 75.66 + ... class="Products.Five.browser.tests.pages.FancyView" 75.67 + ... attribute="view" 75.68 + ... name="fancy" 75.69 + ... permission="zope2.Public" 75.70 + ... /> 75.71 + ... 75.72 + ... </configure>''' 75.73 + >>> zcml.load_string(configure_zcml) 75.74 + 75.75 + >>> from Products.Five.tests.testing.fancycontent import manage_addFancyContent 75.76 + >>> info = manage_addFancyContent(self.folder, 'fancy', '') 75.77 + 75.78 + In the following test we let the original __bobo_traverse__ method 75.79 + kick in: 75.80 + 75.81 + >>> print http(r''' 75.82 + ... GET /test_folder_1_/fancy/something-else HTTP/1.1 75.83 + ... ''') 75.84 + HTTP/1.1 200 OK 75.85 + ... 75.86 + something-else 75.87 + 75.88 + Of course we also need to make sure that Zope 3 style view lookup 75.89 + actually works: 75.90 + 75.91 + >>> print http(r''' 75.92 + ... GET /test_folder_1_/fancy/fancy HTTP/1.1 75.93 + ... ''') 75.94 + HTTP/1.1 200 OK 75.95 + ... 75.96 + Fancy, fancy 75.97 + 75.98 + 75.99 + Clean up: 75.100 + 75.101 + >>> from zope.app.tests.placelesssetup import tearDown 75.102 + >>> tearDown() 75.103 + """ 75.104 + 75.105 +def test_suite(): 75.106 + from Testing.ZopeTestCase import FunctionalDocTestSuite 75.107 + return FunctionalDocTestSuite() 75.108 + 75.109 +if __name__ == '__main__': 75.110 + framework()
76.1 new file mode 100644 76.2 --- /dev/null 76.3 +++ b/configure.zcml 76.4 @@ -0,0 +1,47 @@ 76.5 +<configure xmlns="http://namespaces.zope.org/zope" 76.6 + xmlns:five="http://namespaces.zope.org/five"> 76.7 + 76.8 + <include file="meta.zcml" /> 76.9 + <include file="services.zcml" /> 76.10 + <include file="interfaces.zcml" /> 76.11 + <include file="permissions.zcml" /> 76.12 + <include file="i18n.zcml" /> 76.13 + <include file="deprecated.zcml"/> 76.14 + <include package=".site" /> 76.15 + <include package=".browser" /> 76.16 + <include package=".form" /> 76.17 + <include package=".skin" /> 76.18 + <include package=".utilities" /> 76.19 + 76.20 + <include package="zope.app.event" /> 76.21 + <include package="zope.app.traversing" /> 76.22 + 76.23 + <!-- do 'traditional' traversing by default; needed by ZPT --> 76.24 + <adapter 76.25 + for="*" 76.26 + factory=".traversable.FiveTraversable" 76.27 + provides="zope.app.traversing.interfaces.ITraversable" 76.28 + /> 76.29 + 76.30 + <adapter 76.31 + for="*" 76.32 + factory="zope.app.traversing.adapters.Traverser" 76.33 + provides="zope.app.traversing.interfaces.ITraverser" 76.34 + /> 76.35 + 76.36 + <adapter 76.37 + for="*" 76.38 + factory=".viewable.BrowserDefault" 76.39 + provides=".interfaces.IBrowserDefault" 76.40 + /> 76.41 + 76.42 + <!-- this is really lying, but it's to please checkContainer --> 76.43 + <five:implements class="OFS.ObjectManager.ObjectManager" 76.44 + interface="zope.app.container.interfaces.IContainer" /> 76.45 + 76.46 + <!-- make Zope 2's REQUEST implement the right thing --> 76.47 + <five:implements class="ZPublisher.HTTPRequest.HTTPRequest" 76.48 + interface="zope.publisher.interfaces.browser.IBrowserRequest" 76.49 + /> 76.50 + 76.51 +</configure>
77.1 new file mode 100644 77.2 --- /dev/null 77.3 +++ b/deprecated.zcml 77.4 @@ -0,0 +1,51 @@ 77.5 +<configure xmlns="http://namespaces.zope.org/zope" 77.6 + xmlns:five="http://namespaces.zope.org/five"> 77.7 + 77.8 + <!-- deprecated in core Zope, should be fixed there in Zope 2.9 --> 77.9 + 77.10 + <five:deprecatedManageAddDelete 77.11 + class="AccessControl.User.BasicUserFolder"/> 77.12 + 77.13 + <five:deprecatedManageAddDelete 77.14 + class="App.Factory.Factory"/> 77.15 + <five:deprecatedManageAddDelete 77.16 + class="App.Permission.Permission"/> 77.17 + 77.18 + <five:deprecatedManageAddDelete 77.19 + class="HelpSys.HelpTopic.HelpTopicBase"/> 77.20 + 77.21 + <five:deprecatedManageAddDelete 77.22 + class="OFS.Cache.CacheManager"/> 77.23 + 77.24 + <five:deprecatedManageAddDelete 77.25 + class="Products.OFSP.Draft.Draft"/> 77.26 + <five:deprecatedManageAddDelete 77.27 + class="Products.OFSP.Version.Version"/> 77.28 + 77.29 + <five:deprecatedManageAddDelete 77.30 + class="Products.PythonScripts.PythonScript.PythonScript"/> 77.31 + 77.32 + <five:deprecatedManageAddDelete 77.33 + class="Products.Sessions.BrowserIdManager.BrowserIdManager"/> 77.34 + <five:deprecatedManageAddDelete 77.35 + class="Products.Sessions.SessionDataManager.SessionDataManager"/> 77.36 + 77.37 + <five:deprecatedManageAddDelete 77.38 + class="Products.SiteAccess.VirtualHostMonster.VirtualHostMonster"/> 77.39 + <five:deprecatedManageAddDelete 77.40 + class="Products.SiteAccess.SiteRoot.Traverser"/> 77.41 + 77.42 + <five:deprecatedManageAddDelete 77.43 + class="Products.SiteErrorLog.SiteErrorLog.SiteErrorLog"/> 77.44 + 77.45 + <five:deprecatedManageAddDelete 77.46 + class="Products.ZCatalog.CatalogAwareness.CatalogAware"/> 77.47 + <five:deprecatedManageAddDelete 77.48 + class="Products.ZCatalog.CatalogPathAwareness.CatalogAware"/> 77.49 + 77.50 + <five:deprecatedManageAddDelete 77.51 + class="ZClasses.Property.ZCommonSheet"/> 77.52 + <five:deprecatedManageAddDelete 77.53 + class="ZClasses.ZClass.ZClass"/> 77.54 + 77.55 +</configure>
78.1 new file mode 100644 78.2 --- /dev/null 78.3 +++ b/doc/ZopePublicLicense.txt 78.4 @@ -0,0 +1,54 @@ 78.5 +Zope Public License (ZPL) Version 2.1 78.6 +------------------------------------- 78.7 + 78.8 +A copyright notice accompanies this license document that 78.9 +identifies the copyright holders. 78.10 + 78.11 +This license has been certified as open source. It has also 78.12 +been designated as GPL compatible by the Free Software 78.13 +Foundation (FSF). 78.14 + 78.15 +Redistribution and use in source and binary forms, with or 78.16 +without modification, are permitted provided that the 78.17 +following conditions are met: 78.18 + 78.19 +1. Redistributions in source code must retain the 78.20 + accompanying copyright notice, this list of conditions, 78.21 + and the following disclaimer. 78.22 + 78.23 +2. Redistributions in binary form must reproduce the accompanying 78.24 + copyright notice, this list of conditions, and the 78.25 + following disclaimer in the documentation and/or other 78.26 + materials provided with the distribution. 78.27 + 78.28 +3. Names of the copyright holders must not be used to 78.29 + endorse or promote products derived from this software 78.30 + without prior written permission from the copyright 78.31 + holders. 78.32 + 78.33 +4. The right to distribute this software or to use it for 78.34 + any purpose does not give you the right to use 78.35 + Servicemarks (sm) or Trademarks (tm) of the copyright 78.36 + holders. Use of them is covered by separate agreement 78.37 + with the copyright holders. 78.38 + 78.39 +5. If any files are modified, you must cause the modified 78.40 + files to carry prominent notices stating that you changed 78.41 + the files and the date of any change. 78.42 + 78.43 +Disclaimer 78.44 + 78.45 + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' 78.46 + AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT 78.47 + NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 78.48 + AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 78.49 + NO EVENT SHALL THE COPYRIGHT HOLDERS BE 78.50 + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 78.51 + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 78.52 + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 78.53 + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 78.54 + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 78.55 + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 78.56 + OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 78.57 + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 78.58 + DAMAGE.
79.1 new file mode 100644 79.2 --- /dev/null 79.3 +++ b/doc/directives.txt 79.4 @@ -0,0 +1,197 @@ 79.5 +================================= 79.6 +ZCML Directives supported by Five 79.7 +================================= 79.8 + 79.9 +Five tries to use the Zope 3 ZCML directives where possible, though 79.10 +does sometimes subset the possible attributes. It also introduces a 79.11 +few directives of its own under the ``five`` namespace. 79.12 + 79.13 +Directives are listed per namespace, in alphabetic order. 79.14 + 79.15 +zope ``http://namespaces.zope.org/zope`` 79.16 +======================================== 79.17 + 79.18 +adapter 79.19 +------- 79.20 + 79.21 +Hook an adapter factory to an interface. 79.22 + 79.23 +content 79.24 +------- 79.25 + 79.26 +Declare interface and permissions on content object. Declares Zope 2 79.27 +permissions. 79.28 + 79.29 +permission 79.30 +---------- 79.31 + 79.32 +Way to make Zope 2 permissions available to Five, ``title`` is 79.33 +permission name. 79.34 + 79.35 +redefinePermission 79.36 +------------------ 79.37 + 79.38 +Redefine a permission in included ZCML as another one. 79.39 + 79.40 +service 79.41 +------- 79.42 + 79.43 +Declare a global service 79.44 + 79.45 +serviceType 79.46 +----------- 79.47 + 79.48 +Declare a type of service. 79.49 + 79.50 +skin 79.51 +---- 79.52 + 79.53 +Declare a skin, consisting of layers. 79.54 + 79.55 +utility 79.56 +------- 79.57 + 79.58 +Declare a global utility. 79.59 + 79.60 +interface 79.61 +--------- 79.62 + 79.63 +Register an interface in ZCML. 79.64 + 79.65 +factory 79.66 +------- 79.67 + 79.68 +Register an object factory. 79.69 + 79.70 +modulealias 79.71 +----------- 79.72 + 79.73 +Provide a module under an alias name, e.g. for persistent backward 79.74 +compatability. 79.75 + 79.76 +hook 79.77 +---- 79.78 + 79.79 +Install a hook on a hookable object. 79.80 + 79.81 +browser ``http://namespaces.zope.org/browser`` 79.82 +============================================== 79.83 + 79.84 +page 79.85 +---- 79.86 + 79.87 +Declare a page view for an interface. Permission is a Zope 2 79.88 +permission. 79.89 + 79.90 +pages 79.91 +----- 79.92 + 79.93 +Declare multiple page views for an interface. Permissions are Zope 2 79.94 +permissions. 79.95 + 79.96 +defaultView 79.97 +----------- 79.98 + 79.99 +Declare the name of the view that should be used for the default when viewing 79.100 +the object; i.e. when the object is traversed to without a view. 79.101 + 79.102 +defaultSkin 79.103 +----------- 79.104 + 79.105 +Declare the default skin used. 79.106 + 79.107 +editform 79.108 +-------- 79.109 + 79.110 +Create an edit form based on a schema. 79.111 + 79.112 +addform 79.113 +-------- 79.114 + 79.115 +Create an add form based on a schema. 79.116 + 79.117 +layer 79.118 +----- 79.119 + 79.120 +Declare a layer. 79.121 + 79.122 +menu 79.123 +---- 79.124 + 79.125 +Declare a menu 79.126 + 79.127 +menuItem, menuItems 79.128 +------------------- 79.129 + 79.130 +Declare menuItems 79.131 + 79.132 +five ``http://namespaces.zope.org/five`` 79.133 +======================================== 79.134 + 79.135 +implements 79.136 +---------- 79.137 + 79.138 +Make a class declare it implements an interface. 79.139 + 79.140 +loadProducts 79.141 +------------ 79.142 + 79.143 +Loads ZCML in all Zope 2 products. First processes all ``meta.zcml`` 79.144 +files, then processes all ``configure.zcml`` files. 79.145 + 79.146 +loadProductsOverrides 79.147 +--------------------- 79.148 + 79.149 +Loads overriding ZCML in all products (``overrides.zcml``). 79.150 + 79.151 +traversable 79.152 +----------- 79.153 + 79.154 +Make a Zope 2 content class traversable in the Zope 3 manner using 79.155 +Five. This is used to attached views, resources and other things to 79.156 +Zope 2 objects. 79.157 + 79.158 +defaultViewable 79.159 +--------------- 79.160 + 79.161 +Make a Zope 2 content class use a Zope 3 default view when looking at 79.162 +it without any paths appended to it. This works then instead of 79.163 +``index_html`` in Zope 2. 79.164 + 79.165 +sizable 79.166 +------- 79.167 + 79.168 +Retrieve size information for a Zope 2 content class via a Zope 3 79.169 +style ``ISized`` adapter. 79.170 + 79.171 +containerEvents 79.172 +--------------- 79.173 + 79.174 +Make events be sent for Zope 2 container objects, instead of calling old 79.175 +methods like ``manage_afterAdd``. These old methods will still be called 79.176 +for classes specified in a ``deprecatedManageAddDelete`` directive. 79.177 + 79.178 +deprecatedManageAddDelete 79.179 +------------------------- 79.180 + 79.181 +Specify a class that needs its old deprecated methods like 79.182 +``manage_afterAdd``, ``manage_beforeDelete`` and ``manage_afterClone`` 79.183 +to be called. Modern classes should use event subscribers instead. 79.184 + 79.185 +pagesFromDirectory 79.186 +------------------ 79.187 + 79.188 +Loads all files with .pt extension in a directory as pages. 79.189 + 79.190 +registerClass 79.191 +------------- 79.192 + 79.193 +Registers Five content with Zope 2. 79.194 + 79.195 +localsite 79.196 +--------- 79.197 + 79.198 +Turns a class into an implementation of ``IPossibleSite`` so that its 79.199 +instances can be serve as local sites. Unless otherwise specified, a 79.200 +default implementation's methods will be used to make the class comply 79.201 +with the ``IPossibleSite`` interface.
80.1 new file mode 100644 80.2 --- /dev/null 80.3 +++ b/doc/event.txt 80.4 @@ -0,0 +1,287 @@ 80.5 +Events in Zope 2.9 80.6 +================== 80.7 + 80.8 +Zope 2.9 (and Zope 2.8 when using Five 1.2) introduces a big change: 80.9 +Zope 3 style container events. 80.10 + 80.11 +With container events, you finally have the ability to react to things 80.12 +happening to objects without have to subclass ``manage_afterAdd``, 80.13 +``manage_beforeDelete`` or ``manage_afterClone``. Instead, you just have 80.14 +to register a subscriber for the appropriate event, for instance 80.15 +IObjectAddedEvent, and make it do the work. 80.16 + 80.17 +Indeed, the old methods like ``manage_afterAdd`` are now deprecated, you 80.18 +shouldn't use them anymore. 80.19 + 80.20 +Let's see how to migrate your products. 80.21 + 80.22 +Old product 80.23 +----------- 80.24 + 80.25 +Suppose that in an old product you have code that needs to register 80.26 +through a central tool whenever a document is created. Or it could be 80.27 +indexing itself. Or it could initialize an attribute according to its 80.28 +current path. Code like:: 80.29 + 80.30 + class CoolDocument(...): 80.31 + ... 80.32 + def manage_afterAdd(self, item, container): 80.33 + self.mangled_path = mangle('/'.join(self.getPhysicalPath())) 80.34 + getToolByName(self, 'portal_cool').registerCool(self) 80.35 + super(CoolDocument, self).manage_afterAdd(item, container) 80.36 + 80.37 + def manage_afterClone(self, item): 80.38 + self.mangled_path = mangle('/'.join(self.getPhysicalPath())) 80.39 + getToolByName(self, 'portal_cool').registerCool(self) 80.40 + super(CoolDocument, self).manage_afterClone(item) 80.41 + 80.42 + def manage_beforeDelete(self, item, container): 80.43 + super(CoolDocument, self).manage_beforeDelete(item, container) 80.44 + getToolByName(self, 'portal_cool').unregisterCool(self) 80.45 + 80.46 +This would be the best practice in Zope 2.8. Note the use of ``super()`` 80.47 +to call the base class, which is often omitted because people "know" 80.48 +that SimpleItem for instance doesn't do anything in these methods. 80.49 + 80.50 +If you run this code in Zope 2.9, you will get deprecation warnings, 80.51 +telling you that:: 80.52 + 80.53 + Calling Products.CoolProduct.CoolDocument.CoolDocument.manage_afterAdd 80.54 + is deprecated when using Five, instead use event subscribers or mark 80.55 + the class with <five:deprecatedManageAddDelete/> 80.56 + 80.57 +Using five:deprecatedManageAddDelete 80.58 +------------------------------------ 80.59 + 80.60 +The simplest thing you can do to deal with the deprecation warnings, and 80.61 +have correct behavior, is to add in your products a ``configure.zcml`` 80.62 +file containing:: 80.63 + 80.64 + <configure 80.65 + xmlns="http://namespaces.zope.org/zope" 80.66 + xmlns:five="http://namespaces.zope.org/five"> 80.67 + 80.68 + <five:deprecatedManageAddDelete 80.69 + class="Products.CoolProduct.CoolDocument.CoolDocument"/> 80.70 + 80.71 + </configure> 80.72 + 80.73 +This tells Zope that you acknowledge that your class contains deprecated 80.74 +methods, and ask it to still call them in the proper manner. So Zope 80.75 +will be sending events when an object is added, for instance, and in 80.76 +addition call your old ``manage_afterAdd`` method. 80.77 + 80.78 +One subtlety here is that you may have to modify you methods to just do 80.79 +their work, and not call their super class. This is necessary because 80.80 +proper events are already dispatched to all relevant classes, and the 80.81 +work of the super class will be done trough events, you must not redo it 80.82 +by hand. If you call the super class, you will get a warning, saying for 80.83 +instance:: 80.84 + 80.85 + CoolDocument.manage_afterAdd is deprecated and will be removed in 80.86 + Zope 2.11, you should use an IObjectAddedEvent subscriber instead. 80.87 + 80.88 +The fact that you must "just do your work" is especially important for 80.89 +the rare cases where people subclass the ``manage_afterAdd`` of object 80.90 +managers like folders, and decided to reimplement recursion into the 80.91 +children themselves. If you do that, then there will be two recursions 80.92 +going on in parallel, the one done by events, and the one done by your 80.93 +code. This would be bad. 80.94 + 80.95 +Using subscribers 80.96 +----------------- 80.97 + 80.98 +In the long run, and before Zope 2.11 where ``manage_afterAdd`` and 80.99 +friends will be removed, you will want to use proper subscribers. 80.100 + 80.101 +First, you'll have to write a subscriber that "does the work", for 80.102 +instance:: 80.103 + 80.104 + def addedCoolDocument(ob, event): 80.105 + """A Cool Document was added to a container.""" 80.106 + self.mangled_path = mangle('/'.join(self.getPhysicalPath())) 80.107 + 80.108 +Note that we're not calling the ``portal_cool`` tool anymore, because 80.109 +presumably this tool will also be modified to do its work through 80.110 +events, and will have a similar subscriber doing the necessary 80.111 +``registerCool``. Note also that here we don't care about the event, but 80.112 +in more complex cases we would. 80.113 + 80.114 +Now we have to register our subscriber for our object. To do that, we 80.115 +need to "mark" our object through an interface. We can define in our 80.116 +product's ``interfaces.py``:: 80.117 + 80.118 + from zope.interface import Interface, Attribute 80.119 + 80.120 + class ICoolDocument(Interface): 80.121 + """Cool Document.""" 80.122 + mangled_path = Attribute("Our mangled path.") 80.123 + ... 80.124 + 80.125 +Then the class CoolDocument is marked with this interface:: 80.126 + 80.127 + from zope.interface import implements 80.128 + from Products.CoolProduct.interfaces import ICoolDocument 80.129 + class CoolDocument(...): 80.130 + implements(ICoolDocument) 80.131 + ... 80.132 + 80.133 +Finally we must link the event and the interface to the subscriber using 80.134 +zcml, so in ``configure.zcml`` we'll add:: 80.135 + 80.136 + ... 80.137 + <subscriber 80.138 + for="Products.CoolProduct.interfaces.ICoolDocument 80.139 + zope.app.container.interfaces.IObjectAddedEvent" 80.140 + handler="Products.CoolProduct.CoolDocument.addedCoolDocument" 80.141 + /> 80.142 + ... 80.143 + 80.144 +And that's it, everything is plugged. Note that IObjectAddedEvent takes 80.145 +care of both ``manage_afterAdd`` and ``manage_afterClone``, as it's sent 80.146 +whenever a new object is placed into a container. However this won't 80.147 +take care of moves and renamings, we'll see below how to do that. 80.148 + 80.149 +Event dispatching 80.150 +----------------- 80.151 + 80.152 +When an IObjectEvent (from which all the events we're talking here 80.153 +derive) is initially sent, it concerns one object. For instance, a 80.154 +specific object is removed. The ``event.object`` attribute is this 80.155 +object. 80.156 + 80.157 +To be able to know about removals, we could just subscribe to the 80.158 +appropriate event using a standard event subscriber. In that case, we'd 80.159 +have to filter "by hand" to check if the object removed is of the type 80.160 +we're interested in, which would be a chore. In addition, any subobjects 80.161 +of the removed object wouldn't know what happens to them, and for 80.162 +instance they wouldn't have any way of doing some cleanup before they 80.163 +disappear. 80.164 + 80.165 +To solve these two problems, Zope 3 has an additional mechanism by which 80.166 +any IObjectEvent is redispatched using multi-adapters of the form ``(ob, 80.167 +event)``, so that a subscriber can be specific about the type of object 80.168 +it's interested in. Furthermore, this is done recursively for all 80.169 +sublocations ``ob`` of the initial object. The ``event`` won't change 80.170 +though, and ``event.object`` will still be the original object for which 80.171 +the event was initially sent (this corresponds to ``self`` and ``item`` 80.172 +in the ``manage_afterAdd`` method -- ``self`` is ``ob``, and ``item`` is 80.173 +``event.object``). 80.174 + 80.175 +Understanding the hierarchy of events is important to see how to 80.176 +subscribe to them. 80.177 + 80.178 + * IObjectEvent is the most general. Any event focused on an object 80.179 + derives from this. 80.180 + 80.181 + * IObjectMovedEvent is sent when an object changes location or is 80.182 + renamed. It is quite general, as it also encompasses the case where 80.183 + there's no old location (addition) or no new location (removal). 80.184 + 80.185 + * IObjectAddedEvent and IObjectRemovedEvent both derive from 80.186 + IObjectMovedEvent. 80.187 + 80.188 + * IObjectCopiedEvent is sent just after an object copy is made, but 80.189 + this doesn't mean the object has been put into its new container yet, 80.190 + so it doesn't have a location. 80.191 + 80.192 +There are only a few basic use cases about what one wants to do with 80.193 +respect to events (but you might want to read the full story in 80.194 +Five/tests/event.txt). 80.195 + 80.196 +The first use case is the one where the object has to be aware of its 80.197 +path, like in the CoolDocument example above. That's strictly a Zope 2 80.198 +concern, as Zope 3 has others ways to deal with this. 80.199 + 80.200 +In Zope 2 an object has a new path through creation, copy or move 80.201 +(rename is a kind of move). The events sent during these three 80.202 +operations are varied: creation sends IObjectAddedEvent, copy sends 80.203 +IObjectCopiedEvent then IObjectAddedEvent, and move sends 80.204 +IObjectMovedEvent. 80.205 + 80.206 +So to react to new paths, we have to subscribe to IObjectMovedEvent, but 80.207 +this will also get us any IObjectRemovedEvent, which we'll have to 80.208 +filter out by hand (this is unfortunate, and due to the way the Zope 3 80.209 +interface hierarchy is organized). So to fix the CoolDocument 80.210 +configuration we have to add:: 80.211 + 80.212 + def movedCoolDocument(ob, event): 80.213 + """A Cool Document was moved.""" 80.214 + if not IObjectRemovedEvent.providedBy(event): 80.215 + addedCoolDocument(ob, event) 80.216 + 80.217 +And replace the subscriber with:: 80.218 + 80.219 + ... 80.220 + <subscriber 80.221 + for="Products.CoolProduct.interfaces.ICoolDocument 80.222 + zope.app.container.interfaces.IObjectMovedEvent" 80.223 + handler="Products.CoolProduct.CoolDocument.movedCoolDocument" 80.224 + /> 80.225 + ... 80.226 + 80.227 +The second use case is when the object has to do some cleanup when it is 80.228 +removed from its parent. This used to be in ``manage_beforeDelete``, now 80.229 +we can do the work in a ``removedCoolDocument`` method and just 80.230 +subscribe to IObjectRemovedEvent. But wait, this won't take into account 80.231 +moves... So in the same vein as above, we would have to write:: 80.232 + 80.233 + def movedCoolDocument(ob, event): 80.234 + """A Cool Document was moved.""" 80.235 + if not IObjectRemovedEvent.providedBy(event): 80.236 + addedCoolDocument(ob, event) 80.237 + if not IObjectAddedEvent.providedBy(event): 80.238 + removedCoolDocument(ob, event) 80.239 + 80.240 +The third use case is when your object has to stay registered with some 80.241 +tool, for instance indexed in a catalog, or as above registered with 80.242 +``portal_cool``. Here we have to know the old object's path to 80.243 +unregister it, so we have to be called *before* it is removed. We'll use 80.244 +``IObjectWillBe...`` events, that are sent before the actual operations 80.245 +take place:: 80.246 + 80.247 + from OFS.interfaces import IObjectWillBeAddedEvent 80.248 + def beforeMoveCoolDocument(ob, event): 80.249 + """A Cool Document will be moved.""" 80.250 + if not IObjectWillBeAddedEvent.providedBy(event): 80.251 + getToolByName(ob, 'portal_cool').unregisterCool(ob) 80.252 + 80.253 + def movedCoolDocument(ob, event): 80.254 + """A Cool Document was moved.""" 80.255 + if not IObjectRemovedEvent.providedBy(event): 80.256 + getToolByName(ob, 'portal_cool').registerCool(ob) 80.257 + ... 80.258 + 80.259 +And use an additional subscriber:: 80.260 + 80.261 + ... 80.262 + <subscriber 80.263 + for="Products.CoolProduct.interfaces.ICoolDocument 80.264 + OFS.interfaces.IObjectWillBeMovedEvent" 80.265 + handler="Products.CoolProduct.CoolDocument.beforeMoveCoolDocument" 80.266 + /> 80.267 + ... 80.268 + 80.269 +This has to be done if the tool cannot react by itself to objects being 80.270 +added and removed, which obviously would be better as it's ultimately 80.271 +the tool's responsibility and not the object's. 80.272 + 80.273 +Note that if having tests like:: 80.274 + 80.275 + if not IObjectWillBeAddedEvent.providedBy(event): 80.276 + if not IObjectRemovedEvent.providedBy(event): 80.277 + 80.278 +seems cumbersome (and backwards), it is also possible to check what kind 80.279 +of event you're dealing with using:: 80.280 + 80.281 + if event.oldParent is not None: 80.282 + if event.newParent is not None: 80.283 + 80.284 +(However be careful, the ``oldParent`` and ``newParent`` are the old and 80.285 +new parents *of the original object* for which the event was sent, not 80.286 +of the one to which the event was redispatched using the 80.287 +multi-subscribers we have registered.) 80.288 + 80.289 +The ``IObjectWillBe...`` events are specific to Zope 2 (and imported 80.290 +from ``OFS.interfaces``). Zope 3 doesn't really need them, as object 80.291 +identity is often enough.
81.1 new file mode 100644 81.2 --- /dev/null 81.3 +++ b/doc/features.txt 81.4 @@ -0,0 +1,101 @@ 81.5 +============= 81.6 +Five features 81.7 +============= 81.8 + 81.9 +Five features are mostly Zope 3 features, though Five has some extras, 81.10 +and some limitations. 81.11 + 81.12 +Zope 3 interfaces 81.13 +================= 81.14 + 81.15 +Everything from the ``zope.interface`` package works. Zope 3 81.16 +interfaces are the foundation of the component architecture, and also 81.17 +the foundation of schemas. 81.18 + 81.19 +ZCML 81.20 +==== 81.21 + 81.22 +ZCML is the Zope Configuration Markup Language, an XML application. 81.23 +Zope 3 (and Five) code consists of a lot of components that can be 81.24 +plugged together using ZCML. 81.25 + 81.26 +If you put a ``site.zcml`` in the home directory of your Zope 81.27 +instance, this is the root of the ZCML tree. An example of 81.28 +``site.zcml`` is in ``site.zcml.in``. If you don't place a 81.29 +``site.zcml``, Five falls back on ``fallback.zcml``. 81.30 + 81.31 +ZCML in Five has special directive, ``five:loadProducts``, to load the 81.32 +ZCML (``meta.zcml``, ``configure.zcml``) of all installed Zope 2 81.33 +products, if available. 81.34 + 81.35 +Another special directive, ``five:loadProductsOverrides`` is available 81.36 +to load any overriding ZCML (``overrides.zcml``) in these products. In 81.37 +the ``overrides.zcml`` you can override existing views or adapters, in 81.38 +this or in other products. 81.39 + 81.40 +Adapters 81.41 +======== 81.42 + 81.43 +You can use adapters in Five, just like in Zope 3. 81.44 + 81.45 +Zope 3 views 81.46 +============ 81.47 + 81.48 +Zope 3 views work in Five, including layers and skins. To make them 81.49 +work however, you need to make a Zope 2 class "traversable". This can 81.50 +be done by using the ``five:traversable`` directive in ZCML. 81.51 + 81.52 +Page templates 81.53 +============== 81.54 + 81.55 +Five before release 0.3 used to use Zope 3's page template engine, but 81.56 +in the interests of increased compatibility with Zope 2, we've 81.57 +switched to using Zope 2's. There should be no real difference to any 81.58 +code, however. We may decide to switch back to Zope 3's engine again 81.59 +eventually if we can resolve the compatibility issues. 81.60 + 81.61 +One thing to be aware of is that the page template engine runs 81.62 +completely in trusted mode, just like Python code. That is, as soon as 81.63 +the page template engine is running, no Zope 2 or Zope 3 security 81.64 +checks are made. 81.65 + 81.66 +Edit and add forms 81.67 +================== 81.68 + 81.69 +Five supports edit and add forms. Typical Zope 3 examples of these 81.70 +should work. 81.71 + 81.72 +Security declarations 81.73 +===================== 81.74 + 81.75 +Five aims to eradicate ``declareProtected``, ``ClassSecurityInfo`` and 81.76 +``initializeClass`` from your Zope 2 code. 81.77 + 81.78 +In order to do this, Five provides the Zope 3 way of declaring 81.79 +permissions from ZCML, but uses the Zope 2 mechanisms to actually set 81.80 +them. To declare permissions for methods and templates on views you 81.81 +use the ``permission`` attribute on the ``browser:page`` directive, 81.82 +and specify a Zope 2 permission (given a Zope 3 name). You can find a 81.83 +list of these permissions in ``permissions.zcml`` in Five. The 81.84 +permission check takes place before the view is executed. 81.85 + 81.86 +The ``content`` directive can also be used to declare permissions on 81.87 +Zope 2 content classes. Note however that these permissions will be 81.88 +ignored by views anyway, as they are trusted -- it only serves to 81.89 +protect directly exposed methods on content classes (the python 81.90 +scripts and the ZPublisher). 81.91 + 81.92 +Local Sites 81.93 +=========== 81.94 + 81.95 +Five supports the concept of a local sites and local site managers. 81.96 +See localsite.txt_ for more information. 81.97 + 81.98 +.. _localsite.txt: localsite.html 81.99 + 81.100 +Object events 81.101 +============= 81.102 + 81.103 +Five supports sending Zope 3 object events when objects are added, 81.104 +moved, renamed, copied and deleted. The use of ``manage_afterAdd`` & co 81.105 +methods is deprecated.
82.1 new file mode 100644 82.2 --- /dev/null 82.3 +++ b/doc/i18n.txt 82.4 @@ -0,0 +1,76 @@ 82.5 +Internationalization 82.6 +==================== 82.7 + 82.8 +Translation 82.9 +----------- 82.10 + 82.11 +Five registers its own translation service mockup with the Page 82.12 +Templates machinery and prevents any other product from also doing so. 82.13 +That means, Five always assumes control over ZPT i18n. When a certain 82.14 +domain has not been registered the Zope 3 way, Five's translation 82.15 +service will see that the utility lookup fails and use the next 82.16 +available fallback translation service. In case of no other 82.17 +translation service installed, that is just a dummy fallback. In case 82.18 +you have Localizer and PTS installed, it falls back to that. 82.19 + 82.20 +To register Zope 3 style translation domains, use the following ZCML 82.21 +statement:: 82.22 + 82.23 + <i18n:registerTranslations directory="locales" /> 82.24 + 82.25 +where the 'i18n' prefix is bound to the 82.26 +http://namespaces.zope.org/i18n namespace identifier. The directory 82.27 +(in this case 'locales') should conform to the `standard gettext 82.28 +locale directory layout`__. 82.29 + 82.30 +.. __: http://www.gnu.org/software/gettext/manual/html_chapter/gettext_10.html#SEC148 82.31 + 82.32 + 82.33 +Preferred languages and negotiation 82.34 +----------------------------------- 82.35 + 82.36 +Fallback translation services such as PTS and Localizer have their own 82.37 +way of determining the user-preferred languages and negotiating that 82.38 +with the available languages in the respective domain. Zope 3 82.39 +translation domains typically adapt the request to 82.40 +IUserPreferredLanguages to get a list of preferred languages; then 82.41 +they use the INegotiator utility to negotiate between the preferred 82.42 +and available languages. 82.43 + 82.44 +The goal of the sprint was to allow both fallback translation services 82.45 +(PTS, Localizer) and Zope 3 translation domains come to the same 82.46 +conclusion regarding which language should be chosen. The use case is 82.47 +that you have a site running Localizer or PTS and a bunch of "old" 82.48 +products using either one of those for translation. Now you have an 82.49 +additional, "new" Five-based product using Zope 3 translation domains. 82.50 +Most of the time, a page contains user messages from more than one 82.51 +domain, so you would all domains be translated to the same language. 82.52 + 82.53 + 82.54 +Adjusting behaviour to your environment 82.55 +--------------------------------------- 82.56 + 82.57 +The default behaviour for choosing languages in Five is the one of 82.58 +Zope 3: analyze the Accept-Language HTTP header and nothing more. In 82.59 +addition, Five providees ``IUserPreferredLanguages`` adapters for 82.60 +Localizer and PTS that choose languages the exact same way Localizer 82.61 +or PTS would. So, if you're using Five in a Localizer-environment, 82.62 +you need this in your product's ``overrides.zcml``: 82.63 + 82.64 + <adapter 82.65 + for="zope.publisher.interfaces.http.IHTTPRequest" 82.66 + provides="zope.i18n.interfaces.IUserPreferredLanguages" 82.67 + factory="Products.Five.i18n.LocalizerLanguages" 82.68 + /> 82.69 + 82.70 +If you're using PTS: 82.71 + 82.72 + <adapter 82.73 + for="zope.publisher.interfaces.http.IHTTPRequest" 82.74 + provides="zope.i18n.interfaces.IUserPreferredLanguages" 82.75 + factory="Products.Five.i18n.PTSLanguages" 82.76 + /> 82.77 + 82.78 +That way Zope 3 translation domains will always come to the same 82.79 +conclusion regarding the language as your original translation service 82.80 +would.
83.1 new file mode 100644 83.2 --- /dev/null 83.3 +++ b/doc/localsite.txt 83.4 @@ -0,0 +1,126 @@ 83.5 +Local sites in Five 83.6 +=================== 83.7 + 83.8 +Intro 83.9 +----- 83.10 + 83.11 +Zope 3 has a concept of local sites and site managers. They allow one 83.12 +to locally override component registrations and have components and 83.13 +their configuration be persisted in the ZODB as well as managed 83.14 +through the web interface. 83.15 + 83.16 +By default, Zope 3 has a global site which is configured through ZCML. 83.17 +It provides the fallback for all component look-up. Local sites are 83.18 +typically set during traversal, when the traverser encounters an 83.19 +``ISite`` object. The last encountered ``ISite`` wins. Component 83.20 +look-up will cascade through all the sites in the hierarchy and fall 83.21 +back to the global site where it can finally fail. 83.22 + 83.23 +Five also supports local sites, however by default only local 83.24 +utilities. Local adapters, such as ZODB-based views, could be 83.25 +supported with a custom implementation of the local site manager and 83.26 +local adapter registry. This is not the focus of local sites in Five, 83.27 +though. 83.28 + 83.29 + 83.30 +Turning possible sites into sites 83.31 +--------------------------------- 83.32 + 83.33 +Five uses the same technique as Zope 3 for determining local sites: 83.34 +sites are found during URL traversal. However, since the Zope 2 83.35 +ZPublisher doesn't emit the necessary events by default, Five needs to 83.36 +set a ``BeforeTraverse`` hook on site objects. 83.37 + 83.38 +Setting this hook needs to be done an object-per-object basis and can 83.39 +be performed through the ``manage_site.html`` browser page. This view 83.40 +operates on ``IPossibleSite`` objects (in other words, objects that 83.41 +*can* be sites but aren't yet). It sets the traversal hook on the 83.42 +object and then marks it with the ``ISite`` interface to indicate that 83.43 +it is a real site now, not just a possible site. 83.44 + 83.45 +Note that unlike the Zope 3 equivalent of this view, it does not set 83.46 +the site manager to site; it is assumed that the site already knows 83.47 +how to get its site manager. 83.48 + 83.49 +Also note that in order for the view to work, the object's class needs 83.50 +to be Five-traversable, e.g. with the following ZCML statement: 83.51 + 83.52 + <five:traversable class=".module.MyClass" /> 83.53 + 83.54 + 83.55 +Custom site implementations 83.56 +--------------------------- 83.57 + 83.58 +Anything can be a site, there are no restrictions (sites don't have to 83.59 +be folders, for examples). Sites can also be nested. For all the 83.60 +Component Architecture cares, every object in your URL graph could be 83.61 +a site. 83.62 + 83.63 +The only requirement are two interfaces: 83.64 + 83.65 +``IPossibleSite`` 83.66 + 83.67 + Objects that can potentially be turned into a site need to provide 83.68 + this interface. That requires them to have a ``setSiteManager()`` 83.69 + and ``getSiteManager()`` method for setting and getting the local 83.70 + site manager of that site. The site manager is the registry that 83.71 + takes care of local component look-up. 83.72 + 83.73 +``IFiveSiteManager`` 83.74 + 83.75 + This interface is a slight extension of the ``IServiceService`` or 83.76 + ``ISiteManager`` interface, respectively (the former in Zope X3 83.77 + 3.0, the latter in later versions). It defines the API of a local 83.78 + site manager that is to be used in a Five environment. The site's 83.79 + ``getSiteManager()`` method should return an object providing this 83.80 + interface. 83.81 + 83.82 + 83.83 +Five's default site manager 83.84 +---------------------------- 83.85 + 83.86 +If you want to instantly make your custom class an ``IPossibleSite`` 83.87 +implementation, you can use a default mix-in class from Five, e.g.:: 83.88 + 83.89 + class MySite(OFS.Folder, Products.Five.site.localsite.FiveSite): 83.90 + pass 83.91 + 83.92 +This default implementation of ``IPossibleSite`` features a site 83.93 +manager implementation that knows how to register and look-up local 83.94 +utilities. It does so by adapting the site to 83.95 +``IFiveUtilityRegistry``. 83.96 + 83.97 +The default adapter for this local utility registry simply stores the 83.98 +utilities in a standard OFS Folder on called ``utilities`` on the site 83.99 +object. You probably want to exchange that simple behaviour with 83.100 +something that works better in your application. You can do so by 83.101 +plugging in your own utility registry adapter, e.g.:: 83.102 + 83.103 + <adapter for=".interfaces.IMySite" 83.104 + provides="Products.Five.site.interfaces.IFiveUtilityRegistry" 83.105 + fatory=".module.MyUtilityRegistry" /> 83.106 + 83.107 +All this implementation needs to do is comply with the 83.108 +``IFiveUtilityRegistry`` interface, which essentially means the 83.109 +standard utility look-up methods like ``queryUtility()``, 83.110 +``getUtilitiesFor()``, etc. 83.111 + 83.112 + 83.113 +Turning existing classes into possible sites 83.114 +-------------------------------------------- 83.115 + 83.116 +If you cannot or do not want to modify existing classes to mix in the 83.117 +``FiveSite`` class, you can also use a structured monkey patch via 83.118 +ZCML:: 83.119 + 83.120 + <five:localsite class=".module.MyClass" /> 83.121 + 83.122 +This makes ``MyClass`` an ``IPossibleSite`` and sticks ``FiveSite``'s 83.123 +``getSiteManager()`` and ``setSiteManager()`` methods on the class as 83.124 +well. You can also tell it to use a different site implementation's 83.125 +methods for the monkey patch:: 83.126 + 83.127 + <five:localsite class=".module.MyClass" 83.128 + site_class=".module.MySiteImpl" /> 83.129 + 83.130 +Just make sure that this class implements ``IPossibleSite``.
84.1 new file mode 100644 84.2 --- /dev/null 84.3 +++ b/doc/main.txt 84.4 @@ -0,0 +1,103 @@ 84.5 +Five, the Zope 3 in Zope 2 project 84.6 +================================== 84.7 + 84.8 +What is Five? 84.9 +------------- 84.10 + 84.11 +Five is a Zope 2 product that allows you to integrate Zope 3 84.12 +technologies into Zope 2, today. Five right now allows you to use the 84.13 +following Zope 3 technologies in Zope 2: 84.14 + 84.15 +* Zope 3 interfaces 84.16 + 84.17 +* adapters 84.18 + 84.19 +* pages (views), including skins and layers, and edit and add forms 84.20 + 84.21 +* ZCML 84.22 + 84.23 +It is possible to add Zope 3 style views to your own Zope 2 objects, 84.24 +or to existing ones, even normal Folders! 84.25 + 84.26 +Five works with a straight Zope 2.7 installation, as long as Zope 3 84.27 +has been installed. See Five's INSTALL.txt for more information on how 84.28 +to set it up. Five 1.0 is also already included in Zope 2.8, newer 84.29 +Five releases since are available however. 84.30 + 84.31 +We're in the process of evaluating lots more Zope 3 technologies for 84.32 +integration into Zope 2. This is the right moment for interested Zope 84.33 +2 and Zope 3 developers to jump in. We're looking for cooperation 84.34 +between different Zope 2 projects so that this can be a foundational 84.35 +system for us all. 84.36 + 84.37 +Download 84.38 +-------- 84.39 + 84.40 +2005-10-04 -- We have release Five 1.1! Download it here: 84.41 + 84.42 +http://codespeak.net/z3/five/release/Five-1.1.tgz 84.43 + 84.44 +2005-07-13 -- We have released Five 1.1b! Download it here: 84.45 + 84.46 +http://codespeak.net/z3/five/release/Five-1.1b.tgz 84.47 + 84.48 +2005-07-12 -- We have released Five 1.0.2! This is also the version 84.49 +that will be included in Zope 2.8.1. Download it here: 84.50 + 84.51 +http://codespeak.net/z3/five/release/Five-1.0.2.tgz 84.52 + 84.53 +2005-05-31 -- We have released Five 1.0.1! This is also the version 84.54 +that will be included in Zope 2.8.0. Download it here: 84.55 + 84.56 +http://codespeak.net/z3/five/release/Five-1.0.1.tgz 84.57 + 84.58 +2005-04-27 -- We have released Five 1.0! Download it here: 84.59 + 84.60 +http://codespeak.net/z3/five/release/Five-1.0.tgz 84.61 + 84.62 +And view changes here: 84.63 + 84.64 +http://codespeak.net/z3/five/CHANGES.html 84.65 + 84.66 +2005-03-11 -- We have released Five 0.3! Download it here: 84.67 + 84.68 +http://codespeak.net/z3/five/release/Five-0.3.tgz 84.69 + 84.70 +2004-09-24 -- Five 0.2b is released. Download it here: 84.71 + 84.72 +http://codespeak.net/z3/five/release/Five-0.2b.tgz 84.73 + 84.74 +2004-07-30 -- We have released Five 0.1! Download it here: 84.75 + 84.76 +http://codespeak.net/z3/five/release/Five-0.1.tgz 84.77 + 84.78 +Joining the project 84.79 +------------------- 84.80 + 84.81 +Five is kindly hosted on codespeak.net, and is part of the larger 84.82 +*Zope 3 Base* project that offers an approachable area for 84.83 +developers of Zope 3 related software. 84.84 + 84.85 +Five has a mailing list: 84.86 + 84.87 +http://codespeak.net/mailman/listinfo/z3-five 84.88 + 84.89 +We're also active on IRC, at ``#z3-base`` on freenode. 84.90 + 84.91 +Five is hosted in a subversion repository on codespeak.net. You can 84.92 +browse this on the web here: 84.93 + 84.94 +http://codespeak.net/svn/z3/Five/ 84.95 + 84.96 +You can check out Five using the following subversion command:: 84.97 + 84.98 + svn co http://codespeak.net/svn/z3/Five/trunk Five 84.99 + 84.100 +There's also a checkins mailing list for the Z3 project, here: 84.101 + 84.102 +http://codespeak.net/mailman/listinfo/z3-checkins 84.103 + 84.104 +If you want checkin access, please join the z3-five mailing list or 84.105 +the ``#z3-base`` IRC channel, and ask us there. 84.106 + 84.107 +We hope to hear from you!
85.1 new file mode 100644 85.2 --- /dev/null 85.3 +++ b/doc/manual.txt 85.4 @@ -0,0 +1,339 @@ 85.5 +=========== 85.6 +Five Manual 85.7 +=========== 85.8 + 85.9 +Introduction 85.10 +------------ 85.11 + 85.12 +Five's goal is to let you, the Zope 2 developer, use Zope 3 code in 85.13 +Zope 2. Our aim is to make as much of Zope 3 code work in Zope 2 as 85.14 +possible, while integrating it with Zope 2. 85.15 + 85.16 +Five can be used inside your current Zope 2 project. The benefits are: 85.17 + 85.18 +* availability of Zope 3 technologies in Zope 2 like the component 85.19 + architecture and declarative configuration. 85.20 + 85.21 +* you can gradually evolve your Zope 2 project so it is better 85.22 + positioned for the migration to Zope 3. 85.23 + 85.24 +* you start learning about Zope 3 right now, preparing yourself better 85.25 + for the future. Since Zope 3 is open to contributions, you could 85.26 + even influence your future for the better. 85.27 + 85.28 +Five can also be used to develop new Zope 2 products, though depending 85.29 +on your deployment requirements it might in that case make more sense 85.30 +to develop for Zope 3 directly. 85.31 + 85.32 +Five is only useful on the Python (Product) level in Zope 2, not from 85.33 +within the Zope Management Interface. Five makes no attempt to provide 85.34 +a user interface, but is aimed squarely at the Python developer. 85.35 + 85.36 +Zope 3 interfaces 85.37 +----------------- 85.38 + 85.39 +Interfaces? 85.40 +=========== 85.41 + 85.42 +An interface is simply a description of what an object provides to the 85.43 +world, i.e. its public attribute and methods. It looks very much like 85.44 +a class, but contains no implementation:: 85.45 + 85.46 + from zope.interface import Interface 85.47 + 85.48 + # by convention, all interfaces are prefixed with ``I`` 85.49 + class IElephant(Interface): 85.50 + """An elephant is a big object that barely fits in the cupboard. 85.51 + """ 85.52 + 85.53 + def getAngerLevel(): 85.54 + """Anger level, maximum of 100. 85.55 + 85.56 + The longer the elephant has been in the cupboard, the angrier. 85.57 + """ 85.58 + 85.59 + def isInCupboard(): 85.60 + """Returns true if the elephant is indeed in cupboard. 85.61 + """ 85.62 + 85.63 + def trunkSmash(target): 85.64 + """Smash the target with trunk. 85.65 + 85.66 + The anger level determines the force of the hit. 85.67 + """ 85.68 + 85.69 + def trample(target): 85.70 + """Trample the target. 85.71 + 85.72 + The anger level determines the rate of flattening of the target. 85.73 + """ 85.74 + 85.75 +A concrete class somewhere can now claim that it implements the 85.76 +interface (i.e. its instance will provide the interface):: 85.77 + 85.78 + class PinkElephant: 85.79 + # this says all instances of this class provide IElephant 85.80 + implements(IElephant) 85.81 + 85.82 + def getAngerLevel(self): 85.83 + return 0 # this elephant is peaceful 85.84 + 85.85 + def isInCupboard(self): 85.86 + return False # it's never in a cupboard but can be found in bottles 85.87 + 85.88 + def trunkSmash(self, target): 85.89 + target.tickle() 85.90 + 85.91 + def trample(self, target): 85.92 + target.patOnHead() 85.93 + 85.94 +Interfaces themselves are good for a number of reasons: 85.95 + 85.96 +* They provide API documentation. 85.97 + 85.98 +* They help you make explicit the design of your application, 85.99 + hopefully improving it. 85.100 + 85.101 +* If an object provides an interface, that object is considered to be 85.102 + a *component*. This means you can use Zope 3's component 85.103 + architecture with these objects. 85.104 + 85.105 +In order to use Five, you'll have to make your objects provide 85.106 +interfaces. Sometimes, you cannot change the code of class (as you are 85.107 +not the maintainer), but you still want to make it implement an 85.108 +interface. Five provides a ZCML directive to do this:: 85.109 + 85.110 + <five:implements class="tolkien.Oliphant" 85.111 + implements="interfaces.IElephant" /> 85.112 + 85.113 +Interfaces in Zope 2 versus Zope 3 85.114 +================================== 85.115 + 85.116 +You may be familiar with Zope 2's way of declaring interfaces. Zope 2 85.117 +has used the ``__implements__`` class attribute for interface 85.118 +declarations. Zope 2 cannot detect Zope 3 interfaces and the Zope 3 85.119 +machinery cannot detect Zope 2 interfaces. This is a good thing, as 85.120 +Zope 2 has no way to deal with Zope 3 interfaces, and Zope 3 cannot 85.121 +comprehend Zope 2 interfaces. This means you can safely make a class 85.122 +declare both a Zope 2 and Zope 3 interface independently from each 85.123 +other. It's a rare case where you need this though; you're usually 85.124 +better off just switching to ``implements()`` for your application if 85.125 +you are using Five. 85.126 + 85.127 +Switching from Zope 2 interfaces to Zope 3 interfaces is easy -- just 85.128 +make your interfaces inherit from ``zope.interface.Interface`` instead 85.129 +of ``Interface.Interface`` (or ``Interface.Base``). Next, change all 85.130 +``__implements__`` to ``implements()``. 85.131 + 85.132 +This should get you going and your application may very well still 85.133 +work. Later on, you will also have to change calls to 85.134 +``isImplementedBy`` and such in your application to ``providedBy``, as 85.135 +``isImplementedBy`` has been deprecated (you'll see the 85.136 +DeprecationWarnings in your Zope log). 85.137 + 85.138 +Adapters 85.139 +-------- 85.140 + 85.141 +From a Python programmer's perspective, the immediate thing that Five 85.142 +brings to do the table are adapters. This section goes through some 85.143 +demo code to explain how everything is tied 85.144 +together. ``demo/FiveDemo`` is a demo Product you can install and 85.145 +examine that has all the presented here together. 85.146 + 85.147 +Zope 3 adapters depend on Zope 3 interfaces. To create a Zope 3 85.148 +interface you need to subclass it from 85.149 +``zope.interface.Interface``. Here is an example:: 85.150 + 85.151 + from zope.interface import Interface 85.152 + 85.153 + class IMyInterface(Interface): 85.154 + """This is a Zope 3 interface. 85.155 + """ 85.156 + def someMethod(): 85.157 + """This method does amazing stuff. 85.158 + """ 85.159 + 85.160 +Now to make some class declare that it implements this interface, you 85.161 +need to use the ``implements()`` function in the class:: 85.162 + 85.163 + from zope.interface import implements 85.164 + from interfaces import IMyInterface 85.165 + 85.166 + class MyClass: 85.167 + implements(IMyInterface) 85.168 + 85.169 + def someMethod(self): 85.170 + return "I am alive! Alive!" 85.171 + 85.172 +For an explanation of the relation of Zope 3 interfaces to Zope 2 85.173 +interfaces, see below. 85.174 + 85.175 +Now let's set up the interface that we are adapting to:: 85.176 + 85.177 + class INewInterface(Interface): 85.178 + """The interface we adapt to. 85.179 + """ 85.180 + 85.181 + def anotherMethod(): 85.182 + """This method does more stuff. 85.183 + """ 85.184 + 85.185 +Next we'll work on the class that implements the adapter. The 85.186 +requirement to make a class that is an adapter is very simple; you 85.187 +only need to take a context object as the constructor. The context 85.188 +object is the object being adapted. An example:: 85.189 + 85.190 + from zope.interface import implements 85.191 + from interfaces import INewInterface 85.192 + 85.193 + class MyAdapter: 85.194 + implements(INewInterface) 85.195 + 85.196 + def __init__(self, context): 85.197 + self.context = context 85.198 + 85.199 + def anotherMethod(self): 85.200 + return "We have adapted: %s" % self.context.someMethod() 85.201 + 85.202 +Next, we hook it all up using zcml. If the classes are in a module 85.203 +called ``classes.py`` and the interfaces in a module called 85.204 +``interfaces.py``, we can declare ``MyAdapter`` to be an adapter for 85.205 +``IMyInterface`` to ``INewInterface`` like this (in a file called 85.206 +``configure.zcml``):: 85.207 + 85.208 + <configure xmlns="http://namespaces.zope.org/zope"> 85.209 + 85.210 + <adapter 85.211 + for=".interfaces.IMyInterface" 85.212 + provides=".interfaces.INewInterface" 85.213 + factory=".classes.MyAdapter" /> 85.214 + 85.215 + </configure> 85.216 + 85.217 +Five will automatically pickup ``configure.zcml`` when it's placed in 85.218 +the product's directory. Any object that provides ``IMyInterface`` 85.219 +can now be adapted to ``INewInterface``, like this:: 85.220 + 85.221 + from classes import MyClass 85.222 + from interfaces import INewInterface 85.223 + 85.224 + object = MyClass() 85.225 + adapted = INewInterface(object) 85.226 + print adapted.anotherMethod() 85.227 + 85.228 +Views in Five 85.229 +------------- 85.230 + 85.231 +This section will give a brief introduction on how to use the five 85.232 +view system. ``demo/FiveViewsDemo`` is a demo Product you can install 85.233 +and examine that has all the presented here tied together, please 85.234 +consult it for more details. ``tests/products/FiveTest`` actually 85.235 +contains a more detailed set of test views, trying a number of 85.236 +features. Finally, read up on the way Zope 3 does it. While Five is a 85.237 +subset of Zope 3 functionality and has been adapted to work with Zope 85.238 +2, much of Zope 3's documentation still works. 85.239 + 85.240 +Five enables you to create views for your own objects, or even built-in 85.241 +Zope objects, as long as two things are the case: 85.242 + 85.243 +* The object provides an Zope 3 interface, typically through its class. 85.244 + 85.245 +* The object (typically its class) is made Zope 3 traversable. This 85.246 + allows Zope 3 views, resources and other things to be attached to a 85.247 + Zope 2 object. 85.248 + 85.249 +Typically you give your classes an interface using the ``implements`` 85.250 +directive in the class body:: 85.251 + 85.252 + class MyClass: 85.253 + implements(ISomeInterface) 85.254 + 85.255 +For existing objects that you cannot modify this is not 85.256 +possible. Instead, we provide a ZCML directive to accomplish this. As 85.257 +an example, to make Zope's ``Folder`` (and all its subclasses) 85.258 +implement ``IFolder`` (an interface you defined), you can do the 85.259 +following in ZCML:: 85.260 + 85.261 + <five:implements class="OFS.Folder.Folder" 85.262 + interface=".interfaces.IFolder" /> 85.263 + 85.264 +``five`` in this case refers to the XML namespace for Five, 85.265 +``http://namespace.zope.org/five``. 85.266 + 85.267 +We've provided another ZCML directive to make an object 85.268 +traversable. To make your MyClass traversable, let's assume it is in 85.269 +``mymodule``, in the same package as the zcml file we are editing:: 85.270 + 85.271 + <five:traversable class=".mymodule.MyClass" /> 85.272 + 85.273 +To continue our example, to make Zope's ``Folder`` traversable through 85.274 +Five, you need to declare this in ZCML as well: 85.275 + 85.276 + <five:traversable class="OFS.Folder.Folder"/> 85.277 + 85.278 +This makes Folder traverse in the Zope 3 way first, looking up views 85.279 +and other things, and then if they cannot be found, fall back on the 85.280 +regular Zope 2 traversal. It does this by overriding the 85.281 +``__bobo_traverse__`` hook. Old hooks that are already in place in an 85.282 +object will be stored and become the secondary fallback. This allows 85.283 +the ZMI to work still, but new views can be added on the fly. 85.284 + 85.285 +Note that at the point of writing it is only possible to make an object 85.286 +viewable through ZCML if this object does not already provide its own 85.287 +``__bobo_traverse__`` method. 85.288 + 85.289 +Views in Five are simple classes. The only requirements for a Five 85.290 +view class are: 85.291 + 85.292 +* They need an ``__init__()`` that take a context and a request 85.293 + attribute. Typically this comes from a base class, such as 85.294 + ``BrowserView``. 85.295 + 85.296 +* They need to be initialized with the Zope 2 security system, as 85.297 + otherwise you cannot use the view. 85.298 + 85.299 +* This also means they need to be part of the Zope 2 acquisition 85.300 + system, as this is a requirement for Zope 2 security to 85.301 + function. The ``BrowserView`` base class, available from 85.302 + ``Products.Five``, already inherits from ``Acquisition.Explicit`` to 85.303 + make this be the case. Acquisition is explicit so no attributes can 85.304 + be acquired by accident. 85.305 + 85.306 +An example of a simple view:: 85.307 + 85.308 + from Products.Five import BrowserView 85.309 + 85.310 + class SimpleFolderView(BrowserView): 85.311 + security = ClassSecurityInfo() 85.312 + 85.313 + security.declarePublic('eagle') 85.314 + def eagle(self): 85.315 + """Test 85.316 + """ 85.317 + return "The eagle has landed: %s" % self.context.objectIds() 85.318 + 85.319 + InitializeClass(SimpleFolderView) 85.320 + 85.321 +Note that it is not a good idea to give a view class its own 85.322 +``index_html``, as this confuses Five's view lookup machinery. 85.323 + 85.324 +As you can see, the class is initialized with the Zope 2 security 85.325 +system. This view uses methods in Python, but you can also use other 85.326 +Zope 2 mechanisms such as ``PageTemplateFile``. 85.327 + 85.328 +Finally, we need to hook up the pages through ZCML:: 85.329 + 85.330 + <browser:page 85.331 + for=".interfaces.IFolder" 85.332 + class=".browser.SimpleFolderView" 85.333 + attribute="eagle" 85.334 + name="eagle.txt" 85.335 + permission="zope2.ViewManagementScreens" 85.336 + /> 85.337 + 85.338 +``browser`` in this refers to the XML namespace of Zope 3 for browser 85.339 +related things; it's 85.340 +``http://namespace.zope.org/browser``. ``permission`` declares the 85.341 +Zope 2 permission needs in order to access this view. The file 85.342 +``permissions.zcml`` in Five contains a mapping of Zope 2 permissions 85.343 +to their Zope 3 names.
86.1 new file mode 100644 86.2 --- /dev/null 86.3 +++ b/doc/presentations/five.mgp 86.4 @@ -0,0 +1,127 @@ 86.5 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 86.6 +%deffont "standard" xfont "helvetica-medium-r" 86.7 +%deffont "thick" xfont "helvetica-bold-r" 86.8 +%deffont "typewriter" xfont "courier-medium-r" 86.9 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 86.10 +%% 86.11 +%% Default settings per each line numbers. 86.12 +%% 86.13 +%default 1 area 90 90, leftfill, size 2, fore "gray20", back "white", font "standard", hgap 0 86.14 +%default 2 size 7, vgap 10, prefix " ", ccolor "blue" 86.15 +%default 3 size 2, bar "gray70", vgap 10 86.16 +%default 4 size 5, fore "gray20", vgap 30, prefix " ", font "standard" 86.17 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 86.18 +%% 86.19 +%% Default settings that are applied to TAB-indented lines. 86.20 +%% 86.21 +%tab 1 size 5, vgap 40, prefix " ", icon box "red" 50 86.22 +%tab 2 size 4, vgap 40, prefix " ", icon arc "yellow" 50 86.23 +%tab 3 size 3, vgap 40, prefix " ", icon delta3 "white" 40 86.24 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 86.25 +%page 86.26 + 86.27 +Five - Zope 3 in Zope 2 86.28 + 86.29 + 86.30 + 86.31 + 86.32 +%center 86.33 +Martijn Faassen, Infrae 86.34 +faassen@infrae.com 86.35 + 86.36 +%page 86.37 + 86.38 +Motto 86.39 + 86.40 + 86.41 +It was the dawn of the third age of Zope. The Five project was a dream given form. Its goal: to use Zope 3 technologies in Zope 2.7 by creating a Zope 2 product where Zope 3 and Zope 2 could work out their differences peacefully. 86.42 + 86.43 +(Babylon 5 season 1 intro, creatively quoted) 86.44 + 86.45 +%page 86.46 + 86.47 +Motto 2 86.48 + 86.49 + 86.50 +The Law of Fives states simply that: ALL THINGS HAPPEN IN FIVES, OR ARE DIVISIBLE BY OR ARE MULTIPLES OF FIVE, OR ARE SOMEHOW DIRECTLY OR INDIRECTLY RELATED TO FIVE. 86.51 + 86.52 +THE LAW OF FIVES IS NEVER WRONG. 86.53 + 86.54 +(Principia Discordia) 86.55 + 86.56 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 86.57 +%page 86.58 + 86.59 +The problem 86.60 + 86.61 + 86.62 + We're using Zope 2 in production 86.63 + 86.64 + Zope 2 is showing its age 86.65 + 86.66 + Zope 3 has better ways to do things 86.67 + 86.68 + But can't just switch, we have customers! 86.69 + 86.70 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 86.71 +%page 86.72 + 86.73 +Benefits of using Zope 3 in Zope 2 86.74 + 86.75 + 86.76 + Able to use Zope 3 technologies right away 86.77 + 86.78 + Don't reinvent the wheel/APIs 86.79 + 86.80 + Better prepared for Zope 3 transition 86.81 + 86.82 + Evolution, not revolution 86.83 + 86.84 + Convergence, not divergence 86.85 + 86.86 +%page 86.87 + 86.88 +What works now? 86.89 + 86.90 + 86.91 + Interfaces (zope.interface) 86.92 + 86.93 + Schema (zope.schema) 86.94 + 86.95 + ZCML (zope.configuration) 86.96 + 86.97 + Adapters (zope.component) 86.98 + 86.99 + Views, including layers, skins (zope.component) 86.100 + 86.101 +%page 86.102 + 86.103 +Brief demo 86.104 + 86.105 + 86.106 + Show ZCML, adapters and views in action 86.107 + 86.108 +%page 86.109 + 86.110 +Next? 86.111 + 86.112 + 86.113 + Utilities (global ones should work) 86.114 + 86.115 + Forms 86.116 + 86.117 + Views (improve the current system) 86.118 + 86.119 + Who knows? 86.120 + 86.121 +%page 86.122 + 86.123 +Plans 86.124 + 86.125 + 86.126 + Relicense from BSD to generic ZPL 2.1 86.127 + 86.128 + Move from CVS at Infrae into SVN at codespeak.net 86.129 + 86.130 + Convergence; join us! 86.131 +
87.1 new file mode 100644 87.2 --- /dev/null 87.3 +++ b/doc/presentations/five_directions.mgp 87.4 @@ -0,0 +1,88 @@ 87.5 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 87.6 +%deffont "standard" xfont "helvetica-medium-r" 87.7 +%deffont "thick" xfont "helvetica-bold-r" 87.8 +%deffont "typewriter" xfont "courier-medium-r" 87.9 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 87.10 +%% 87.11 +%% Default settings per each line numbers. 87.12 +%% 87.13 +%default 1 area 90 90, leftfill, size 2, fore "gray20", back "white", font "standard", hgap 0 87.14 +%default 2 size 7, vgap 10, prefix " ", ccolor "blue" 87.15 +%default 3 size 2, bar "gray70", vgap 10 87.16 +%default 4 size 5, fore "gray20", vgap 30, prefix " ", font "standard" 87.17 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 87.18 +%% 87.19 +%% Default settings that are applied to TAB-indented lines. 87.20 +%% 87.21 +%tab 1 size 5, vgap 40, prefix " ", icon box "red" 50 87.22 +%tab 2 size 4, vgap 40, prefix " ", icon arc "yellow" 50 87.23 +%tab 3 size 3, vgap 40, prefix " ", icon delta3 "white" 40 87.24 +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 87.25 +%page 87.26 + 87.27 +Five - Zope 3 in Zope 2 87.28 + 87.29 + 87.30 + 87.31 + 87.32 +%center 87.33 +Martijn Faassen, Infrae 87.34 +faassen@infrae.com 87.35 +Five developer 87.36 + 87.37 +%page 87.38 + 87.39 +Five future directions 87.40 + 87.41 + 87.42 + What might happen 87.43 + 87.44 +%page 87.45 + 87.46 +Unique id service support 87.47 + 87.48 +