[Zope-Checkins] SVN: Zope/trunk/ Merged philikon-aq branch into
trunk, yeah!
Hanno Schlichting
plone at hannosch.info
Sat Apr 26 13:46:47 EDT 2008
Log message for revision 85767:
Merged philikon-aq branch into trunk, yeah!
Changed:
U Zope/trunk/doc/CHANGES.txt
U Zope/trunk/lib/python/AccessControl/ImplPython.py
U Zope/trunk/lib/python/AccessControl/Owned.py
U Zope/trunk/lib/python/AccessControl/Permission.py
U Zope/trunk/lib/python/AccessControl/PermissionMapping.py
U Zope/trunk/lib/python/AccessControl/Role.py
U Zope/trunk/lib/python/AccessControl/User.py
U Zope/trunk/lib/python/AccessControl/cAccessControl.c
U Zope/trunk/lib/python/Acquisition/_Acquisition.c
U Zope/trunk/lib/python/Acquisition/tests.py
U Zope/trunk/lib/python/App/FactoryDispatcher.py
U Zope/trunk/lib/python/OFS/FindSupport.py
U Zope/trunk/lib/python/OFS/PropertySheets.py
U Zope/trunk/lib/python/OFS/SimpleItem.py
U Zope/trunk/lib/python/OFS/Traversable.py
U Zope/trunk/lib/python/OFS/ZDOM.py
U Zope/trunk/lib/python/Products/Five/bbb.py
U Zope/trunk/lib/python/Products/Five/browser/__init__.py
U Zope/trunk/lib/python/Products/Five/browser/absoluteurl.py
U Zope/trunk/lib/python/Products/Five/browser/adding.py
U Zope/trunk/lib/python/Products/Five/browser/configure.zcml
U Zope/trunk/lib/python/Products/Five/browser/metaconfigure.py
U Zope/trunk/lib/python/Products/Five/browser/pagetemplatefile.py
U Zope/trunk/lib/python/Products/Five/browser/providerexpression.py
U Zope/trunk/lib/python/Products/Five/browser/resource.py
A Zope/trunk/lib/python/Products/Five/browser/tests/aqlegacy.py
A Zope/trunk/lib/python/Products/Five/browser/tests/aqlegacy.zcml
A Zope/trunk/lib/python/Products/Five/browser/tests/aqlegacy_ftest.txt
A Zope/trunk/lib/python/Products/Five/browser/tests/legacymanager.pt
A Zope/trunk/lib/python/Products/Five/browser/tests/legacyprovider.pt
U Zope/trunk/lib/python/Products/Five/browser/tests/pages.py
U Zope/trunk/lib/python/Products/Five/browser/tests/pages.txt
U Zope/trunk/lib/python/Products/Five/browser/tests/pages.zcml
U Zope/trunk/lib/python/Products/Five/browser/tests/pages_ftest.txt
U Zope/trunk/lib/python/Products/Five/browser/tests/provider.txt
U Zope/trunk/lib/python/Products/Five/browser/tests/resource_ftest.txt
U Zope/trunk/lib/python/Products/Five/browser/tests/template_variables.pt
U Zope/trunk/lib/python/Products/Five/browser/tests/test_absoluteurl.py
U Zope/trunk/lib/python/Products/Five/browser/tests/test_pages.py
U Zope/trunk/lib/python/Products/Five/component/__init__.py
U Zope/trunk/lib/python/Products/Five/doc/manual.txt
U Zope/trunk/lib/python/Products/Five/form/objectwidget.py
U Zope/trunk/lib/python/Products/Five/formlib/formbase.py
U Zope/trunk/lib/python/Products/Five/i18n.py
U Zope/trunk/lib/python/Products/Five/site/tests/test_utility.py
U Zope/trunk/lib/python/Products/Five/viewlet/README.txt
U Zope/trunk/lib/python/Products/Five/viewlet/directives.txt
U Zope/trunk/lib/python/Products/Five/viewlet/manager.py
U Zope/trunk/lib/python/Products/Five/viewlet/viewlet.py
U Zope/trunk/lib/python/Products/PageTemplates/PageTemplateFile.py
U Zope/trunk/lib/python/Products/PageTemplates/ZopePageTemplate.py
U Zope/trunk/lib/python/Shared/DC/Scripts/Bindings.py
U Zope/trunk/lib/python/ZPublisher/BaseRequest.py
U Zope/trunk/lib/python/ZPublisher/HTTPRequest.py
U Zope/trunk/lib/python/ZPublisher/mapply.py
U Zope/trunk/lib/python/ZPublisher/tests/testBaseRequest.py
U Zope/trunk/lib/python/ZPublisher/tests/testHTTPRequest.py
A Zope/trunk/lib/python/ZPublisher/tests/test_mapply.py
U Zope/trunk/lib/python/Zope2/App/startup.py
-=-
Modified: Zope/trunk/doc/CHANGES.txt
===================================================================
--- Zope/trunk/doc/CHANGES.txt 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/doc/CHANGES.txt 2008-04-26 17:46:46 UTC (rev 85767)
@@ -78,7 +78,11 @@
Features added
- - Zope2 startup: Zope will now send DatabaseOpened and
+ - Acquisition has been made aware of __parent__ pointers. This allows
+ direct access to many Zope 3 classes without the need to mixin
+ Acquisition base classes for the security to work.
+
+ - Zope2 startup: Zope will now send DatabaseOpened and
ProcessStarting events.
- Testing.ZopeTestCase: Introduced a "ZopeLite" test layer, making it
Modified: Zope/trunk/lib/python/AccessControl/ImplPython.py
===================================================================
--- Zope/trunk/lib/python/AccessControl/ImplPython.py 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/AccessControl/ImplPython.py 2008-04-26 17:46:46 UTC (rev 85767)
@@ -17,10 +17,8 @@
import string
from logging import getLogger
-from Acquisition import aq_base
-from Acquisition import aq_parent
-from Acquisition import aq_inner
-from Acquisition import aq_acquire
+from Acquisition import aq_base, aq_parent, aq_inner, aq_acquire
+from Acquisition import aq_inContextOf
from ExtensionClass import Base
from zope.interface import implements
@@ -98,10 +96,10 @@
else:
r = r + list(roles)
- object = getattr(object, 'aq_inner', None)
+ object = aq_inner(object)
if object is None:
break
- object = object.aq_parent
+ object = aq_parent(object)
if r is None:
if _embed_permission_in_roles:
@@ -295,7 +293,7 @@
raise Unauthorized(name, value)
else:
# Try to acquire roles
- try: roles = container.aq_acquire('__roles__')
+ try: roles = aq_acquire(container, '__roles__')
except AttributeError:
if containerbase is not accessedbase:
if self._verbose:
@@ -840,17 +838,10 @@
# This is a strange rule, though
# it doesn't cause any security holes. SDH
return 1
- if not hasattr(object, 'aq_inContextOf'):
- if hasattr(object, 'im_self'):
- # This is a method. Grab its self.
- object=object.im_self
- if not hasattr(object, 'aq_inContextOf'):
- # object is not wrapped, therefore we
- # can't determine context.
- # Fail the access attempt. Otherwise
- # this would be a security hole.
- return None
- if not object.aq_inContextOf(ucontext, 1):
+ if hasattr(object, 'im_self'):
+ # This is a method. Grab its self.
+ object=object.im_self
+ if not aq_inContextOf(object, ucontext, 1):
if 'Shared' in object_roles:
# Old role setting. Waaa
object_roles=user._shared_roles(object)
Modified: Zope/trunk/lib/python/AccessControl/Owned.py
===================================================================
--- Zope/trunk/lib/python/AccessControl/Owned.py 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/AccessControl/Owned.py 2008-04-26 17:46:46 UTC (rev 85767)
@@ -21,7 +21,7 @@
from AccessControl import getSecurityManager, Unauthorized
from AccessControl.Permissions import view_management_screens
from AccessControl.Permissions import take_ownership
-from Acquisition import aq_get, aq_parent, aq_base
+from Acquisition import aq_get, aq_parent, aq_base, aq_inner
from requestmethod import requestmethod
from zope.interface import implements
@@ -236,12 +236,12 @@
def manage_fixupOwnershipAfterAdd(self):
# Sigh, get the parent's _owner
- parent=getattr(self, 'aq_parent', None)
+ parent=getattr(self, '__parent__', None)
if parent is not None: _owner=aq_get(parent, '_owner', None, 1)
else: _owner=None
if (_owner is None and
- ((not hasattr(self, 'aq_parent')) or
+ ((getattr(self, '__parent__', None) is None) or
(not hasattr(self, 'getPhysicalRoot'))
)
):
@@ -298,13 +298,13 @@
return None
uid=user.getId()
if uid is None: return uid
- db=user.aq_inner.aq_parent
+ db=aq_parent(aq_inner(user))
path=[absattr(db.id)]
root=db.getPhysicalRoot()
while 1:
db=getattr(db,'aq_inner', None)
if db is None: break
- db=db.aq_parent
+ db=aq_parent(db)
if db is root: break
id=db.id
if not isinstance(id, str):
Modified: Zope/trunk/lib/python/AccessControl/Permission.py
===================================================================
--- Zope/trunk/lib/python/AccessControl/Permission.py 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/AccessControl/Permission.py 2008-04-26 17:46:46 UTC (rev 85767)
@@ -17,6 +17,7 @@
import string, Products, Globals
+from Acquisition import aq_base
name_trans=filter(lambda c, an=string.letters+string.digits+'_': c not in an,
map(chr,range(256)))
@@ -36,8 +37,7 @@
self.name=name
self._p='_'+string.translate(name,name_trans)+"_Permission"
self.data=data
- if hasattr(obj, 'aq_base'): obj=obj.aq_base
- self.obj=obj
+ self.obj=aq_base(obj)
self.default=default
def getRoles(self, default=_marker):
Modified: Zope/trunk/lib/python/AccessControl/PermissionMapping.py
===================================================================
--- Zope/trunk/lib/python/AccessControl/PermissionMapping.py 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/AccessControl/PermissionMapping.py 2008-04-26 17:46:46 UTC (rev 85767)
@@ -105,7 +105,7 @@
return r
def _isBeingAccessedAsZClassDefinedInstanceMethod(self):
- p=getattr(self,'aq_parent',None)
+ p=getattr(self,'__parent__',None)
if p is None: return 0 # Not wrapped
base=getattr(p, 'aq_base', None)
return type(base) is PermissionMapper
Modified: Zope/trunk/lib/python/AccessControl/Role.py
===================================================================
--- Zope/trunk/lib/python/AccessControl/Role.py 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/AccessControl/Role.py 2008-04-26 17:46:46 UTC (rev 85767)
@@ -188,7 +188,7 @@
if userObj:
break
else:
- current = current.aq_parent
+ current = current.__parent__
newSecurityManager(None, userObj) # necessary?
@@ -414,7 +414,7 @@
raise OverflowError
for name in unl:
dict[name]=1
- item = getattr(item, 'aq_parent', _notfound)
+ item = getattr(item, '__parent__', _notfound)
if item is _notfound:
break
keys=dict.keys()
@@ -511,9 +511,9 @@
for role in roles:
if not dup(role):
dict[role]=1
- if not hasattr(obj, 'aq_parent'):
+ if getattr(obj, '__parent__', None) is None:
break
- obj=obj.aq_parent
+ obj=obj.__parent__
x=x+1
roles=dict.keys()
roles.sort()
Modified: Zope/trunk/lib/python/AccessControl/User.py
===================================================================
--- Zope/trunk/lib/python/AccessControl/User.py 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/AccessControl/User.py 2008-04-26 17:46:46 UTC (rev 85767)
@@ -20,6 +20,9 @@
import socket
from base64 import decodestring
+from Acquisition import aq_base
+from Acquisition import aq_parent
+from Acquisition import aq_inContextOf
from Acquisition import Implicit
from App.Management import Navigation, Tabs
from Globals import DTMLFile, MessageDialog, Persistent, PersistentMapping
@@ -106,7 +109,7 @@
for r in dict.get(userid, []):
local[r]=1
inner = getattr(object, 'aq_inner', object)
- parent = getattr(inner, 'aq_parent', None)
+ parent = getattr(inner, '__parent__', None)
if parent is not None:
object = parent
continue
@@ -148,10 +151,10 @@
else:
try: return r+list(roles)
except: return r
- if hasattr(parent, 'aq_parent'):
+ if getattr(parent, '__parent__', None) is not None:
while hasattr(parent.aq_self,'aq_self'):
- parent=parent.aq_self
- parent=parent.aq_parent
+ parent = parent.aq_self
+ parent = aq_parent(parent)
else: return r
def _check_context(self, object):
@@ -160,19 +163,15 @@
# to prevent "stealing" access through acquisition tricks.
# Return true if in context, false if not or if context
# cannot be determined (object is not wrapped).
- parent = getattr(self, 'aq_parent', None)
- context = getattr(parent, 'aq_parent', None)
+ parent = getattr(self, '__parent__', None)
+ context = getattr(parent, '__parent__', None)
if context is not None:
if object is None:
return 1
- if not hasattr(object, 'aq_inContextOf'):
- if hasattr(object, 'im_self'):
- # This is a method. Grab its self.
- object=object.im_self
- if not hasattr(object, 'aq_inContextOf'):
- # Object is not wrapped, so return false.
- return 0
- return object.aq_inContextOf(context, 1)
+ if hasattr(object, 'im_self'):
+ # This is a method. Grab its self.
+ object=object.im_self
+ return aq_inContextOf(object, context, 1)
# This is lame, but required to keep existing behavior.
return 1
@@ -230,7 +229,7 @@
return 1
return 0
inner = getattr(inner_obj, 'aq_inner', inner_obj)
- parent = getattr(inner, 'aq_parent', None)
+ parent = getattr(inner, '__parent__', None)
if parent is not None:
inner_obj = parent
continue
@@ -751,11 +750,11 @@
request.RESPONSE.notFoundError('no default view (root default view'
' was probably deleted)')
n = request.steps[-1]
- # default to accessed and container as v.aq_parent
+ # default to accessed and container as v.__parent__
a = c = request['PARENTS'][0]
# try to find actual container
inner = getattr(v, 'aq_inner', v)
- innerparent = getattr(inner, 'aq_parent', None)
+ innerparent = getattr(inner, '__parent__', None)
if innerparent is not None:
# this is not a method, we needn't treat it specially
c = innerparent
@@ -763,8 +762,8 @@
# this is a method, we need to treat it specially
c = v.im_self
c = getattr(v, 'aq_inner', v)
- request_container = getattr(request['PARENTS'][-1], 'aq_parent', [])
- # if pub's aq_parent or container is the request container, it
+ request_container = getattr(request['PARENTS'][-1], '__parent__', [])
+ # if pub's __parent__ or container is the request container, it
# means pub was accessed from the root
if a is request_container:
a = request['PARENTS'][-1]
@@ -775,7 +774,7 @@
def _isTop(self):
try:
- return self.aq_parent.aq_base.isTopLevelPrincipiaApplicationObject
+ return aq_base(aq_parent(self)).isTopLevelPrincipiaApplicationObject
except:
return 0
@@ -990,8 +989,8 @@
def manage_afterAdd(self, item, container):
if item is self:
- if hasattr(self, 'aq_base'): self=self.aq_base
- container.__allow_groups__=self
+ self = aq_base(self)
+ container.__allow_groups__ = self
def __creatable_by_emergency_user__(self): return 1
Modified: Zope/trunk/lib/python/AccessControl/cAccessControl.c
===================================================================
--- Zope/trunk/lib/python/AccessControl/cAccessControl.c 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/AccessControl/cAccessControl.c 2008-04-26 17:46:46 UTC (rev 85767)
@@ -1878,13 +1878,11 @@
/*
- object = getattr(object, 'aq_inner', None)
+ object = aq_inner(object)
if object is None:
break
- object = object.aq_parent
+ object = aq_parent(object)
*/
- if (! aq_isWrapper(object))
- break;
{
PyObject *tobj = aq_inner(object);
if (tobj == NULL)
@@ -1895,8 +1893,6 @@
if (object == Py_None)
break;
- if (! aq_isWrapper(object))
- break;
tobj = aq_parent(object);
if (tobj == NULL)
goto end;
Modified: Zope/trunk/lib/python/Acquisition/_Acquisition.c
===================================================================
--- Zope/trunk/lib/python/Acquisition/_Acquisition.c 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/Acquisition/_Acquisition.c 2008-04-26 17:46:46 UTC (rev 85767)
@@ -38,7 +38,8 @@
*py__long__, *py__float__, *py__oct__, *py__hex__,
*py__getitem__, *py__setitem__, *py__delitem__,
*py__getslice__, *py__setslice__, *py__delslice__, *py__contains__,
- *py__len__, *py__of__, *py__call__, *py__repr__, *py__str__, *py__cmp__;
+ *py__len__, *py__of__, *py__call__, *py__repr__, *py__str__, *py__cmp__,
+ *py__parent__;
static PyObject *Acquired=0;
@@ -82,7 +83,7 @@
INIT_PY_NAME(__repr__);
INIT_PY_NAME(__str__);
INIT_PY_NAME(__cmp__);
-
+ INIT_PY_NAME(__parent__);
#undef INIT_PY_NAME
}
@@ -414,23 +415,49 @@
Wrapper_findattr(Wrapper *self, PyObject *oname,
PyObject *filter, PyObject *extra, PyObject *orig,
int sob, int sco, int explicit, int containment)
+/*
+ Parameters:
+
+ sob
+ Search self->obj for the 'oname' attribute
+
+ sco
+ Search self->container for the 'oname' attribute
+
+ explicit
+ Explicitly acquire 'oname' attribute from container (assumed with
+ implicit acquisition wrapper)
+
+ containment
+ Use the innermost wrapper ("aq_inner") for looking up the 'oname'
+ attribute.
+*/
{
PyObject *r, *v, *tb;
char *name="";
if (PyString_Check(oname)) name=PyString_AS_STRING(oname);
- if (*name=='a' && name[1]=='q' && name[2]=='_')
- if ((r=Wrapper_special(self, name+3, oname)))
- {
- if (filter)
- switch(apply_filter(filter,OBJECT(self),oname,r,extra,orig))
- {
- case -1: return NULL;
- case 1: return r;
- }
- else return r;
- }
- else PyErr_Clear();
+ if ((*name=='a' && name[1]=='q' && name[2]=='_') ||
+ (strcmp(name, "__parent__")==0))
+ {
+ /* __parent__ is an alias to aq_parent */
+ if (strcmp(name, "__parent__")==0)
+ name = "parent";
+ else
+ name = name + 3;
+
+ if ((r=Wrapper_special(self, name, oname)))
+ {
+ if (filter)
+ switch(apply_filter(filter,OBJECT(self),oname,r,extra,orig))
+ {
+ case -1: return NULL;
+ case 1: return r;
+ }
+ else return r;
+ }
+ else PyErr_Clear();
+ }
else if (*name=='_' && name[1]=='_' &&
(strcmp(name+2,"reduce__")==0 ||
strcmp(name+2,"reduce_ex__")==0 ||
@@ -477,6 +504,7 @@
Py_XDECREF(r); Py_XDECREF(v); Py_XDECREF(tb);
r=NULL;
}
+ /* normal attribute lookup */
else if ((r=PyObject_GetAttr(self->obj,oname)))
{
if (r==Acquired)
@@ -511,6 +539,7 @@
PyErr_Clear();
}
+ /* Lookup has failed, acquire it from parent. */
if (sco && (*name != '_' || explicit))
return Wrapper_acquire(self, oname, filter, extra, orig, explicit,
containment);
@@ -524,24 +553,35 @@
PyObject *filter, PyObject *extra, PyObject *orig,
int explicit, int containment)
{
- PyObject *r;
+ PyObject *r, *v, *tb;
int sob=1, sco=1;
if (self->container)
{
+ /* If the container has an acquisition wrapper itself, we'll use
+ Wrapper_findattr to progress further. */
if (isWrapper(self->container))
{
if (self->obj && isWrapper(self->obj))
{
- /* Try to optimize search by recognizing repeated obs in path */
+ /* Try to optimize search by recognizing repeated
+ objects in path. */
if (WRAPPER(self->obj)->container==
WRAPPER(self->container)->container)
sco=0;
else if (WRAPPER(self->obj)->container==
WRAPPER(self->container)->obj)
sob=0;
- }
+ }
+ /* Don't search the container when the container of the
+ container is the same object as 'self'. */
+ if (WRAPPER(self->container)->container == WRAPPER(self)->obj)
+ {
+ sco=0;
+ containment=1;
+ }
+
r=Wrapper_findattr((Wrapper*)self->container,
oname, filter, extra, orig, sob, sco, explicit,
containment);
@@ -549,8 +589,46 @@
if (r && has__of__(r)) ASSIGN(r,__of__(r,OBJECT(self)));
return r;
}
+ /* If the container has a __parent__ pointer, we create an
+ acquisition wrapper for it accordingly. Then we can proceed
+ with Wrapper_findattr, just as if the container had an
+ acquisition wrapper in the first place (see above). */
+ else if ((r = PyObject_GetAttr(self->container, py__parent__)))
+ {
+ ASSIGN(self->container, newWrapper(self->container, r,
+ (PyTypeObject*)&Wrappertype));
+
+ /* Don't search the container when the parent of the parent
+ is the same object as 'self' */
+ if (WRAPPER(r)->obj == WRAPPER(self)->obj)
+ sco=0;
+
+ Py_DECREF(r); /* don't need __parent__ anymore */
+
+ r=Wrapper_findattr((Wrapper*)self->container,
+ oname, filter, extra, orig, sob, sco, explicit,
+ containment);
+ /* There's no need to DECREF the wrapper here because it's
+ not stored in self->container, thus 'self' owns its
+ reference now */
+ return r;
+ }
+ /* The container is the end of the acquisition chain; if we
+ can't look up the attribute here, we can't look it up at
+ all. */
else
{
+ /* We need to clean up the AttributeError from the previous
+ getattr (because it has clearly failed). */
+ PyErr_Fetch(&r,&v,&tb);
+ if (r && (r != PyExc_AttributeError))
+ {
+ PyErr_Restore(r,v,tb);
+ return NULL;
+ }
+ Py_XDECREF(r); Py_XDECREF(v); Py_XDECREF(tb);
+ r=NULL;
+
if ((r=PyObject_GetAttr(self->container,oname))) {
if (r == Acquired) {
Py_DECREF(r);
@@ -618,8 +696,8 @@
/* Allow assignment to parent, to change context. */
if (PyString_Check(oname)) name=PyString_AS_STRING(oname);
- if (*name=='a' && name[1]=='q' && name[2]=='_'
- && strcmp(name+3,"parent")==0)
+ if ((*name=='a' && name[1]=='q' && name[2]=='_'
+ && strcmp(name+3,"parent")==0) || (strcmp(name, "__parent__")==0))
{
Py_XINCREF(v);
ASSIGN(self->container, v);
@@ -1112,57 +1190,18 @@
# endif
}
+/* forward declaration so that we can use it in Wrapper_inContextOf */
+static PyObject * capi_aq_inContextOf(PyObject *self, PyObject *o, int inner);
static PyObject *
Wrapper_inContextOf(Wrapper *self, PyObject *args)
{
- PyObject *subob, *o, *c;
+ PyObject *o;
+
int inner=1;
-
UNLESS(PyArg_ParseTuple(args,"O|i",&o,&inner)) return NULL;
- if (inner) {
- /* subob = self */
- subob = OBJECT(self);
-
- /* o = aq_base(o) */
- while (isWrapper(o) && WRAPPER(o)->obj) o=WRAPPER(o)->obj;
-
- /* while 1: */
- while (1) {
-
- /* if aq_base(subob) is o: return 1 */
- c = subob;
- while (isWrapper(c) && WRAPPER(c)->obj) c = WRAPPER(c)->obj;
- if (c == o) return PyInt_FromLong(1);
-
- /* self = aq_inner(subob) */
- /* if self is None: break */
- if (isWrapper(subob)) {
- self = WRAPPER(subob);
- while (self->obj && isWrapper(self->obj))
- self = WRAPPER(self->obj);
- }
- else break;
-
- /* subob = aq_parent(self) */
- /* if subob is None: break */
- if (self->container)
- subob = self->container;
- else break;
- }
- }
- else {
- /* Follow wrappers instead. */
- c = OBJECT(self);
- while (1) {
- if (c==o) return PyInt_FromLong(1);
- if (c && isWrapper(c)) c=WRAPPER(c)->container;
- else break;
- }
- }
-
- return PyInt_FromLong(0);
+ return capi_aq_inContextOf((PyObject*)self, o, inner);
}
PyObject *
@@ -1332,8 +1371,7 @@
capi_aq_acquire(PyObject *self, PyObject *name, PyObject *filter,
PyObject *extra, int explicit, PyObject *defalt, int containment)
{
-
- PyObject *result;
+ PyObject *result, *v, *tb;
if (filter==Py_None) filter=0;
@@ -1343,22 +1381,46 @@
WRAPPER(self), name, filter, extra, OBJECT(self),1,
explicit ||
WRAPPER(self)->ob_type==(PyTypeObject*)&Wrappertype,
- explicit, containment);
-
- /* Not wrapped and no filter, so just getattr */
- if (! filter) return PyObject_GetAttr(self, name);
+ explicit, containment);
+ /* Not wrapped; check if we have a __parent__ pointer. If that's
+ the case, create a wrapper and pretend it's business as usual. */
+ else if ((result = PyObject_GetAttr(self, py__parent__)))
+ {
+ self = newWrapper(self, result, (PyTypeObject*)&Wrappertype);
+ Py_DECREF(result); /* don't need __parent__ anymore */
+ result = Wrapper_findattr(WRAPPER(self), name, filter, extra,
+ OBJECT(self), 1, 1, explicit, containment);
+ /* Get rid of temporary wrapper */
+ Py_DECREF(self);
+ return result;
+ }
+ /* No wrapper and no __parent__, so just getattr. */
+ else
+ {
+ /* Clean up the AttributeError from the previous getattr
+ (because it has clearly failed). */
+ PyErr_Fetch(&result,&v,&tb);
+ if (result && (result != PyExc_AttributeError))
+ {
+ PyErr_Restore(result,v,tb);
+ return NULL;
+ }
+ Py_XDECREF(result); Py_XDECREF(v); Py_XDECREF(tb);
- /* Crap, we've got to construct a wrapper so we can use Wrapper_findattr */
- UNLESS (self=newWrapper(self, Py_None, (PyTypeObject*)&Wrappertype))
- return NULL;
+ if (! filter) return PyObject_GetAttr(self, name);
+
+ /* Crap, we've got to construct a wrapper so we can use
+ Wrapper_findattr */
+ UNLESS (self=newWrapper(self, Py_None, (PyTypeObject*)&Wrappertype))
+ return NULL;
- result=Wrapper_findattr(WRAPPER(self), name, filter, extra, OBJECT(self),
- 1, 1, explicit, containment);
+ result=Wrapper_findattr(WRAPPER(self), name, filter, extra, OBJECT(self),
+ 1, 1, explicit, containment);
- /* get rid of temp wrapper */
- Py_DECREF(self);
-
- return result;
+ /* Get rid of temporary wrapper */
+ Py_DECREF(self);
+ return result;
+ }
}
static PyObject *
@@ -1384,13 +1446,35 @@
static PyObject *
capi_aq_get(PyObject *self, PyObject *name, PyObject *defalt, int containment)
{
- PyObject *result = NULL;
+ PyObject *result = NULL, *v, *tb;
/* We got a wrapped object, so business as usual */
if (isWrapper(self))
result=Wrapper_findattr(WRAPPER(self), name, 0, 0, OBJECT(self), 1, 1, 1,
- containment);
+ containment);
+ /* Not wrapped; check if we have a __parent__ pointer. If that's
+ the case, create a wrapper and pretend it's business as usual. */
+ else if ((result = PyObject_GetAttr(self, py__parent__)))
+ {
+ self=newWrapper(self, result, (PyTypeObject*)&Wrappertype);
+ Py_DECREF(result); /* don't need __parent__ anymore */
+ result=Wrapper_findattr(WRAPPER(self), name, 0, 0, OBJECT(self),
+ 1, 1, 1, containment);
+ Py_DECREF(self); /* Get rid of temporary wrapper. */
+ }
else
- result=PyObject_GetAttr(self, name);
+ {
+ /* Clean up the AttributeError from the previous getattr
+ (because it has clearly failed). */
+ PyErr_Fetch(&result,&v,&tb);
+ if (result && (result != PyExc_AttributeError))
+ {
+ PyErr_Restore(result,v,tb);
+ return NULL;
+ }
+ Py_XDECREF(result); Py_XDECREF(v); Py_XDECREF(tb);
+
+ result=PyObject_GetAttr(self, name);
+ }
if (! result && defalt)
{
@@ -1453,13 +1537,34 @@
static PyObject *
capi_aq_parent(PyObject *self)
{
- PyObject *result=Py_None;
+ PyObject *result, *v, *tb;
if (isWrapper(self) && WRAPPER(self)->container)
- result=WRAPPER(self)->container;
+ {
+ result=WRAPPER(self)->container;
+ Py_INCREF(result);
+ return result;
+ }
+ else if ((result=PyObject_GetAttr(self, py__parent__)))
+ /* We already own the reference to result (PyObject_GetAttr gives
+ it to us), no need to INCREF here */
+ return result;
+ else
+ {
+ /* We need to clean up the AttributeError from the previous
+ getattr (because it has clearly failed) */
+ PyErr_Fetch(&result,&v,&tb);
+ if (result && (result != PyExc_AttributeError))
+ {
+ PyErr_Restore(result,v,tb);
+ return NULL;
+ }
+ Py_XDECREF(result); Py_XDECREF(v); Py_XDECREF(tb);
- Py_INCREF(result);
- return result;
+ result=Py_None;
+ Py_INCREF(result);
+ return result;
+ }
}
static PyObject *
@@ -1535,7 +1640,7 @@
static PyObject *
capi_aq_chain(PyObject *self, int containment)
{
- PyObject *result;
+ PyObject *result, *v, *tb;
UNLESS (result=PyList_New(0)) return NULL;
@@ -1558,9 +1663,28 @@
}
}
else
- if (PyList_Append(result, self) < 0)
- goto err;
+ {
+ if (PyList_Append(result, self) < 0)
+ goto err;
+ if ((self=PyObject_GetAttr(self, py__parent__)))
+ {
+ Py_DECREF(self); /* We don't need our own reference. */
+ if (self!=Py_None)
+ continue;
+ }
+ else
+ {
+ PyErr_Fetch(&self,&v,&tb);
+ if (self && (self != PyExc_AttributeError))
+ {
+ PyErr_Restore(self,v,tb);
+ return NULL;
+ }
+ Py_XDECREF(self); Py_XDECREF(v); Py_XDECREF(tb);
+ }
+ }
+
break;
}
@@ -1582,6 +1706,53 @@
return capi_aq_chain(self, containment);
}
+static PyObject *
+capi_aq_inContextOf(PyObject *self, PyObject *o, int inner)
+{
+ PyObject *next, *c;
+
+ /* next = self
+ o = aq_base(o) */
+ next = self;
+ while (isWrapper(o) && WRAPPER(o)->obj)
+ o=WRAPPER(o)->obj;
+
+ while (1) {
+
+ /* if aq_base(next) is o: return 1 */
+ c = next;
+ while (isWrapper(c) && WRAPPER(c)->obj) c = WRAPPER(c)->obj;
+ if (c == o) return PyInt_FromLong(1);
+
+ if (inner)
+ {
+ self = capi_aq_inner(next);
+ Py_DECREF(self); /* We're not holding on to the inner wrapper */
+ if (self == Py_None) break;
+ }
+ else
+ self = next;
+
+ next = capi_aq_parent(self);
+ Py_DECREF(next); /* We're not holding on to the parent */
+ if (next == Py_None) break;
+ }
+
+ return PyInt_FromLong(0);
+}
+
+static PyObject *
+module_aq_inContextOf(PyObject *ignored, PyObject *args)
+{
+ PyObject *self, *o;
+ int inner=1;
+
+ UNLESS (PyArg_ParseTuple(args, "OO|i", &self, &o, &inner))
+ return NULL;
+
+ return capi_aq_inContextOf(self, o, inner);
+}
+
static struct PyMethodDef methods[] = {
{"aq_acquire", (PyCFunction)module_aq_acquire, METH_VARARGS|METH_KEYWORDS,
"aq_acquire(ob, name [, filter, extra, explicit]) -- "
@@ -1599,10 +1770,13 @@
"aq_self(ob) -- Get the object with the outermost wrapper removed"},
{"aq_inner", (PyCFunction)module_aq_inner, METH_VARARGS,
"aq_inner(ob) -- "
- "Get the object with alll but the innermost wrapper removed"},
+ "Get the object with all but the innermost wrapper removed"},
{"aq_chain", (PyCFunction)module_aq_chain, METH_VARARGS,
"aq_chain(ob [, containment]) -- "
"Get a list of objects in the acquisition environment"},
+ {"aq_inContextOf", (PyCFunction)module_aq_inContextOf, METH_VARARGS,
+ "aq_inContextOf(base, ob [, inner]) -- "
+ "Determine whether the object is in the acquisition context of base."},
{NULL, NULL}
};
Modified: Zope/trunk/lib/python/Acquisition/tests.py
===================================================================
--- Zope/trunk/lib/python/Acquisition/tests.py 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/Acquisition/tests.py 2008-04-26 17:46:46 UTC (rev 85767)
@@ -357,6 +357,11 @@
...
AttributeError: aq_parent
+ >>> c.__parent__
+ Traceback (most recent call last):
+ ...
+ AttributeError: __parent__
+
>>> Acquisition.aq_acquire(c, 'id')
'unwrapped'
>>> Acquisition.aq_acquire(c, 'x')
@@ -452,7 +457,14 @@
>>> a.b.c.aq_inContextOf(a.b.c)
1
+ >>> Acquisition.aq_inContextOf(a.b.c, a)
+ 1
+ >>> Acquisition.aq_inContextOf(a.b.c, a.b)
+ 1
+ >>> Acquisition.aq_inContextOf(a.b.c, a.b.c)
+ 1
+
>>> a.b.c.aq_acquire('y')
42
@@ -533,6 +545,13 @@
>>> show(Acquisition.aq_self(a.b.c))
c
+ A wrapper's __parent__ attribute (which is equivalent to its
+ aq_parent attribute) points to the Acquisition parent.
+
+ >>> a.b.c.__parent__ == a.b.c.aq_parent
+ True
+ >>> a.b.c.__parent__ == a.b
+ True
"""
def test__of__exception():
@@ -1201,7 +1220,7 @@
"""
-def old_tests():
+def test_aq_inContextOf():
"""
>>> from ExtensionClass import Base
>>> import Acquisition
@@ -1213,6 +1232,9 @@
... def hi(self):
... print "%s()" % self.__class__.__name__, self.color
+ >>> class Location(object):
+ ... __parent__ = None
+
>>> b=B()
>>> b.a=A()
>>> b.a.hi()
@@ -1242,25 +1264,52 @@
>>> b.c == c
1
+ >>> l = Location()
+ >>> l.__parent__ = b.c
>>> def checkContext(self, o):
... # Python equivalent to aq_inContextOf
... from Acquisition import aq_base, aq_parent, aq_inner
- ... subob = self
+ ... next = self
... o = aq_base(o)
... while 1:
- ... if aq_base(subob) is o: return 1
- ... self = aq_inner(subob)
- ... if self is None: break
- ... subob = aq_parent(self)
- ... if subob is None: break
+ ... if aq_base(next) is o:
+ ... return 1
+ ... self = aq_inner(next)
+ ... if self is None:
+ ... break
+ ... next = aq_parent(self)
+ ... if next is None:
+ ... break
+ ... return 0
>>> checkContext(b.c, b)
1
>>> not checkContext(b.c, b.a)
1
-
+
+ >>> checkContext(l, b)
+ 1
+ >>> checkContext(l, b.c)
+ 1
+ >>> not checkContext(l, b.a)
+ 1
+
+ Acquisition.aq_inContextOf works the same way:
+
+ >>> Acquisition.aq_inContextOf(b.c, b)
+ 1
+ >>> Acquisition.aq_inContextOf(b.c, b.a)
+ 0
+
+ >>> Acquisition.aq_inContextOf(l, b)
+ 1
+ >>> Acquisition.aq_inContextOf(l, b.c)
+ 1
+ >>> Acquisition.aq_inContextOf(l, b.a)
+ 0
+
>>> b.a.aq_inContextOf(b)
1
>>> b.c.aq_inContextOf(b)
@@ -1271,12 +1320,12 @@
1
>>> b.c.d.aq_inContextOf(b.c)
1
- >>> not b.c.aq_inContextOf(foo)
- 1
- >>> not b.c.aq_inContextOf(b.a)
- 1
- >>> not b.a.aq_inContextOf('somestring')
- 1
+ >>> b.c.aq_inContextOf(foo)
+ 0
+ >>> b.c.aq_inContextOf(b.a)
+ 0
+ >>> b.a.aq_inContextOf('somestring')
+ 0
"""
def test_AqAlg():
@@ -1389,7 +1438,7 @@
...
TypeError: __init__() takes exactly 2 arguments (1 given)
- We can reassign aq_parent
+ We can reassign aq_parent / __parent__ on a wrapper:
>>> x = B()
>>> x.color = 'green'
@@ -1397,6 +1446,20 @@
>>> w.color
'green'
+ >>> y = B()
+ >>> y.color = 'blue'
+ >>> w.__parent__ = y
+ >>> w.color
+ 'blue'
+
+ Note that messing with the wrapper won't in any way affect the
+ wrapped object:
+
+ >>> Acquisition.aq_base(w).__parent__
+ Traceback (most recent call last):
+ ...
+ AttributeError: __parent__
+
>>> w = ImplicitAcquisitionWrapper()
Traceback (most recent call last):
...
@@ -1664,6 +1727,434 @@
"""
+class Location(object):
+ __parent__ = None
+
+class ECLocation(ExtensionClass.Base):
+ __parent__ = None
+
+def test___parent__no_wrappers():
+ """
+ Acquisition also works with objects that aren't wrappers, as long
+ as they have __parent__ pointers. Let's take a hierarchy like
+ z --isParent--> y --isParent--> x:
+
+ >>> x = Location()
+ >>> y = Location()
+ >>> z = Location()
+ >>> x.__parent__ = y
+ >>> y.__parent__ = z
+
+ and some attributes that we want to acquire:
+
+ >>> x.hello = 'world'
+ >>> y.foo = 42
+ >>> z.foo = 43 # this should not be found
+ >>> z.bar = 3.145
+
+ ``aq_acquire`` works as we know it from implicit/acquisition
+ wrappers:
+
+ >>> Acquisition.aq_acquire(x, 'hello')
+ 'world'
+ >>> Acquisition.aq_acquire(x, 'foo')
+ 42
+ >>> Acquisition.aq_acquire(x, 'bar')
+ 3.145
+
+ as does ``aq_get``:
+
+ >>> Acquisition.aq_get(x, 'hello')
+ 'world'
+ >>> Acquisition.aq_get(x, 'foo')
+ 42
+ >>> Acquisition.aq_get(x, 'bar')
+ 3.145
+
+ and ``aq_parent``:
+
+ >>> Acquisition.aq_parent(x) is y
+ True
+ >>> Acquisition.aq_parent(y) is z
+ True
+
+ as well as ``aq_chain``:
+
+ >>> Acquisition.aq_chain(x) == [x, y, z]
+ True
+ """
+
+def test_implicit_wrapper_as___parent__():
+ """
+ Let's do the same test again, only now not all objects are of the
+ same kind and link to each other via __parent__ pointers. The
+ root is a stupid ExtensionClass object:
+
+ >>> class Root(ExtensionClass.Base):
+ ... bar = 3.145
+ >>> z = Root()
+
+ The intermediate parent is an object that supports implicit
+ acquisition. We bind it to the root via the __of__ protocol:
+
+ >>> class Impl(Acquisition.Implicit):
+ ... foo = 42
+ >>> y = Impl().__of__(z)
+
+ The child object is again a simple object with a simple __parent__
+ pointer:
+
+ >>> x = Location()
+ >>> x.hello = 'world'
+ >>> x.__parent__ = y
+
+ ``aq_acquire`` works as expected from implicit/acquisition
+ wrappers:
+
+ >>> Acquisition.aq_acquire(x, 'hello')
+ 'world'
+ >>> Acquisition.aq_acquire(x, 'foo')
+ 42
+ >>> Acquisition.aq_acquire(x, 'bar')
+ 3.145
+
+ as does ``aq_get``:
+
+ >>> Acquisition.aq_get(x, 'hello')
+ 'world'
+ >>> Acquisition.aq_get(x, 'foo')
+ 42
+ >>> Acquisition.aq_get(x, 'bar')
+ 3.145
+
+ and ``aq_parent``:
+
+ >>> Acquisition.aq_parent(x) is y
+ True
+ >>> Acquisition.aq_parent(y) is z
+ True
+
+ as well as ``aq_chain``:
+
+ >>> Acquisition.aq_chain(x) == [x, y, z]
+ True
+
+ Note that also the (implicit) acquisition wrapper has a __parent__
+ pointer, which is automatically computed from the acquisition
+ container (it's identical to aq_parent):
+
+ >>> y.__parent__ is z
+ True
+
+ Just as much as you can assign to aq_parent, you can also assign
+ to __parent__ to change the acquisition context of the wrapper:
+
+ >>> newroot = Root()
+ >>> y.__parent__ = newroot
+ >>> y.__parent__ is z
+ False
+ >>> y.__parent__ is newroot
+ True
+
+ Note that messing with the wrapper won't in any way affect the
+ wrapped object:
+
+ >>> Acquisition.aq_base(y).__parent__
+ Traceback (most recent call last):
+ ...
+ AttributeError: __parent__
+ """
+
+def test_explicit_wrapper_as___parent__():
+ """
+ Let's do this test yet another time, with an explicit wrapper:
+
+ >>> class Root(ExtensionClass.Base):
+ ... bar = 3.145
+ >>> z = Root()
+
+ The intermediate parent is an object that supports implicit
+ acquisition. We bind it to the root via the __of__ protocol:
+
+ >>> class Expl(Acquisition.Explicit):
+ ... foo = 42
+ >>> y = Expl().__of__(z)
+
+ The child object is again a simple object with a simple __parent__
+ pointer:
+
+ >>> x = Location()
+ >>> x.hello = 'world'
+ >>> x.__parent__ = y
+
+ ``aq_acquire`` works as expected from implicit/acquisition
+ wrappers:
+
+ >>> Acquisition.aq_acquire(x, 'hello')
+ 'world'
+ >>> Acquisition.aq_acquire(x, 'foo')
+ 42
+ >>> Acquisition.aq_acquire(x, 'bar')
+ 3.145
+
+ as does ``aq_get``:
+
+ >>> Acquisition.aq_get(x, 'hello')
+ 'world'
+ >>> Acquisition.aq_get(x, 'foo')
+ 42
+ >>> Acquisition.aq_get(x, 'bar')
+ 3.145
+
+ and ``aq_parent``:
+
+ >>> Acquisition.aq_parent(x) is y
+ True
+ >>> Acquisition.aq_parent(y) is z
+ True
+
+ as well as ``aq_chain``:
+
+ >>> Acquisition.aq_chain(x) == [x, y, z]
+ True
+
+ Note that also the (explicit) acquisition wrapper has a __parent__
+ pointer, which is automatically computed from the acquisition
+ container (it's identical to aq_parent):
+
+ >>> y.__parent__ is z
+ True
+
+ Just as much as you can assign to aq_parent, you can also assign
+ to __parent__ to change the acquisition context of the wrapper:
+
+ >>> newroot = Root()
+ >>> y.__parent__ = newroot
+ >>> y.__parent__ is z
+ False
+ >>> y.__parent__ is newroot
+ True
+
+ Note that messing with the wrapper won't in any way affect the
+ wrapped object:
+
+ >>> Acquisition.aq_base(y).__parent__
+ Traceback (most recent call last):
+ ...
+ AttributeError: __parent__
+ """
+
+def test_implicit_wrapper_has_nonwrapper_as_aq_parent():
+ """Let's do this the other way around: The root and the
+ intermediate parent is an object that doesn't support acquisition,
+
+ >>> y = ECLocation()
+ >>> z = Location()
+ >>> y.__parent__ = z
+ >>> y.foo = 42
+ >>> z.foo = 43 # this should not be found
+ >>> z.bar = 3.145
+
+ only the outmost object does:
+
+ >>> class Impl(Acquisition.Implicit):
+ ... hello = 'world'
+ >>> x = Impl().__of__(y)
+
+ Again, acquiring objects works as usual:
+
+ >>> Acquisition.aq_acquire(x, 'hello')
+ 'world'
+ >>> Acquisition.aq_acquire(x, 'foo')
+ 42
+ >>> Acquisition.aq_acquire(x, 'bar')
+ 3.145
+
+ as does ``aq_get``:
+
+ >>> Acquisition.aq_get(x, 'hello')
+ 'world'
+ >>> Acquisition.aq_get(x, 'foo')
+ 42
+ >>> Acquisition.aq_get(x, 'bar')
+ 3.145
+
+ and ``aq_parent``:
+
+ >>> Acquisition.aq_parent(x) == y
+ True
+ >>> x.aq_parent == y
+ True
+ >>> x.aq_parent.aq_parent == z
+ True
+ >>> Acquisition.aq_parent(y) is z
+ True
+
+ as well as ``aq_chain``:
+
+ >>> Acquisition.aq_chain(x) == [x, y, z]
+ True
+ >>> x.aq_chain == [x, y, z]
+ True
+
+ Because the outmost object, ``x``, is wrapped in an implicit
+ acquisition wrapper, we can also use direct attribute access:
+
+ >>> x.hello
+ 'world'
+ >>> x.foo
+ 42
+ >>> x.bar
+ 3.145
+ """
+
+def test_explicit_wrapper_has_nonwrapper_as_aq_parent():
+ """Let's do this the other way around: The root and the
+ intermediate parent is an object that doesn't support acquisition,
+
+ >>> y = ECLocation()
+ >>> z = Location()
+ >>> y.__parent__ = z
+ >>> y.foo = 42
+ >>> z.foo = 43 # this should not be found
+ >>> z.bar = 3.145
+
+ only the outmost object does:
+
+ >>> class Expl(Acquisition.Explicit):
+ ... hello = 'world'
+ >>> x = Expl().__of__(y)
+
+ Again, acquiring objects works as usual:
+
+ >>> Acquisition.aq_acquire(x, 'hello')
+ 'world'
+ >>> Acquisition.aq_acquire(x, 'foo')
+ 42
+ >>> Acquisition.aq_acquire(x, 'bar')
+ 3.145
+
+ as does ``aq_get``:
+
+ >>> Acquisition.aq_get(x, 'hello')
+ 'world'
+ >>> Acquisition.aq_get(x, 'foo')
+ 42
+ >>> Acquisition.aq_get(x, 'bar')
+ 3.145
+
+ and ``aq_parent``:
+
+ >>> Acquisition.aq_parent(x) == y
+ True
+ >>> x.aq_parent == y
+ True
+ >>> x.aq_parent.aq_parent == z
+ True
+ >>> Acquisition.aq_parent(y) is z
+ True
+
+ as well as ``aq_chain``:
+
+ >>> Acquisition.aq_chain(x) == [x, y, z]
+ True
+ >>> x.aq_chain == [x, y, z]
+ True
+ """
+
+def test___parent__aq_parent_circles():
+ """
+ As a general safety belt, Acquisition won't follow a mixture of
+ circular __parent__ pointers and aq_parent wrappers. These can
+ occurr when code that uses implicit acquisition wrappers meets
+ code that uses __parent__ pointers.
+
+ >>> class Impl(Acquisition.Implicit):
+ ... hello = 'world'
+
+ >>> class Impl2(Acquisition.Implicit):
+ ... hello = 'world2'
+ ... only = 'here'
+
+ >>> x = Impl()
+ >>> y = Impl2().__of__(x)
+ >>> x.__parent__ = y
+
+ >>> x.__parent__.aq_base is y.aq_base
+ True
+
+ >>> x.__parent__.__parent__ is x
+ True
+
+ >>> x.hello
+ 'world'
+ >>> Acquisition.aq_acquire(x, 'hello')
+ 'world'
+
+ >>> x.only
+ Traceback (most recent call last):
+ ...
+ AttributeError: only
+ >>> Acquisition.aq_acquire(x, 'only')
+ 'here'
+
+ >>> Acquisition.aq_acquire(x, 'non_existant_attr')
+ Traceback (most recent call last):
+ ...
+ AttributeError: non_existant_attr
+
+ >>> Acquisition.aq_acquire(y, 'non_existant_attr')
+ Traceback (most recent call last):
+ ...
+ AttributeError: non_existant_attr
+
+ >>> x.non_existant_attr
+ Traceback (most recent call last):
+ ...
+ AttributeError: non_existant_attr
+
+ >>> y.non_existant_attr
+ Traceback (most recent call last):
+ ...
+ AttributeError: non_existant_attr
+
+ """
+
+def test___parent__parent__circles():
+ """
+ Acquisition won't follow circular __parent__ references:
+
+ >>> class Impl(Acquisition.Implicit):
+ ... hello = 'world'
+
+ >>> class Impl2(Acquisition.Implicit):
+ ... hello = 'world2'
+ ... only = 'here'
+
+ >>> x = Impl()
+ >>> y = Impl2()
+ >>> x.__parent__ = y
+ >>> y.__parent__ = x
+
+ >>> x.__parent__.__parent__ is x
+ True
+
+ >>> Acquisition.aq_acquire(x, 'hello')
+ 'world'
+ >>> Acquisition.aq_acquire(x, 'only')
+ 'here'
+
+ >>> Acquisition.aq_acquire(x, 'non_existant_attr')
+ Traceback (most recent call last):
+ ...
+ AttributeError: non_existant_attr
+
+ >>> Acquisition.aq_acquire(y, 'non_existant_attr')
+ Traceback (most recent call last):
+ ...
+ AttributeError: non_existant_attr
+ """
+
import unittest
from zope.testing.doctest import DocTestSuite, DocFileSuite
Modified: Zope/trunk/lib/python/App/FactoryDispatcher.py
===================================================================
--- Zope/trunk/lib/python/App/FactoryDispatcher.py 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/App/FactoryDispatcher.py 2008-04-26 17:46:46 UTC (rev 85767)
@@ -66,7 +66,7 @@
_owner=UnownableOwner
def __init__(self, product, dest, REQUEST=None):
- if hasattr(product,'aq_base'): product=product.aq_base
+ product = Acquisition.aq_base(product)
self._product=product
self._d=dest
if REQUEST is not None:
@@ -100,7 +100,7 @@
m=d[name]
w=getattr(m, '_permissionMapper', None)
if w is not None:
- m=aqwrap(m, getattr(w,'aq_base',w), self)
+ m=aqwrap(m, Acquisition.aq_base(w), self)
return m
Modified: Zope/trunk/lib/python/OFS/FindSupport.py
===================================================================
--- Zope/trunk/lib/python/OFS/FindSupport.py 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/OFS/FindSupport.py 2008-04-26 17:46:46 UTC (rev 85767)
@@ -22,6 +22,7 @@
from AccessControl.DTML import RestrictedDTML
from AccessControl.Permission import name_trans
from AccessControl.Permissions import view_management_screens
+from Acquisition import aq_base
from DateTime import DateTime
from DocumentTemplate.DT_Util import Eval
from DocumentTemplate.DT_Util import InstanceDict, TemplateDict
@@ -92,9 +93,7 @@
md=td()
obj_expr=(Eval(obj_expr), md, md._push, md._pop)
- base=obj
- if hasattr(obj, 'aq_base'):
- base=obj.aq_base
+ base = aq_base(obj)
if hasattr(base, 'objectItems'):
try: items=obj.objectItems()
@@ -118,9 +117,7 @@
if hasattr(ob, '_p_changed') and (ob._p_changed == None):
dflag=1
- if hasattr(ob, 'aq_base'):
- bs=ob.aq_base
- else: bs=ob
+ bs = aq_base(ob)
if (
(not obj_ids or absattr(bs.getId()) in obj_ids)
and
@@ -200,9 +197,7 @@
md=td()
obj_expr=(Eval(obj_expr), md, md._push, md._pop)
- base=obj
- if hasattr(obj, 'aq_base'):
- base=obj.aq_base
+ base = aq_base(obj)
if not hasattr(base, 'objectItems'):
return result
@@ -221,10 +216,7 @@
if hasattr(ob, '_p_changed') and (ob._p_changed == None):
dflag=1
- if hasattr(ob, 'aq_base'):
- bs=ob.aq_base
- else: bs=ob
-
+ bs = aq_base(ob)
if (
(not obj_ids or absattr(bs.getId()) in obj_ids)
and
Modified: Zope/trunk/lib/python/OFS/PropertySheets.py
===================================================================
--- Zope/trunk/lib/python/OFS/PropertySheets.py 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/OFS/PropertySheets.py 2008-04-26 17:46:46 UTC (rev 85767)
@@ -20,13 +20,14 @@
from ZPublisher.Converters import type_converters
from Globals import InitializeClass
from Globals import DTMLFile, MessageDialog
+from Acquisition import aq_base
+from Acquisition import aq_parent
from Acquisition import Implicit, Explicit
from App.Common import rfc1123_date, iso8601_date
from webdav.common import urlbase
from ExtensionClass import Base
from Globals import Persistent
from Traversable import Traversable
-from Acquisition import aq_base
from AccessControl import ClassSecurityInfo
from AccessControl.Permissions import access_contents_information
from AccessControl.Permissions import manage_properties
@@ -71,7 +72,7 @@
pre=pre+'/'
r=[]
- for d in self.aq_parent.aq_parent.manage_options:
+ for d in aq_parent(aq_parent(self)).manage_options:
path=d['action']
option={'label': d['label'],
'action': pre+path,
@@ -92,7 +93,7 @@
self, script, path)
def meta_type(self):
- try: return self.aq_parent.aq_parent.meta_type
+ try: return aq_parent(aq_parent(self)).meta_type
except: return ''
@@ -489,7 +490,7 @@
pass
def v_self(self):
- return self.aq_parent.aq_parent
+ return aq_parent(aq_parent(self))
class DefaultProperties(Virtual, PropertySheet, View):
@@ -635,7 +636,7 @@
return (self.webdav,)
def __propsets__(self):
- propsets=self.aq_parent.__propsets__
+ propsets = aq_parent(self).__propsets__
__traceback_info__= propsets, type(propsets)
return self._get_defaults() + propsets
@@ -684,17 +685,17 @@
security.declareProtected(manage_properties, 'addPropertySheet')
def addPropertySheet(self, propset):
- propsets=self.aq_parent.__propsets__
- propsets=propsets+(propset,)
- self.aq_parent.__propsets__=propsets
+ propsets = aq_parent(self).__propsets__
+ propsets = propsets+(propset,)
+ aq_parent(self).__propsets__ = propsets
security.declareProtected(manage_properties, 'delPropertySheet')
def delPropertySheet(self, name):
result=[]
- for propset in self.aq_parent.__propsets__:
+ for propset in aq_parent(self).__propsets__:
if propset.getId() != name and propset.xml_namespace() != name:
result.append(propset)
- self.aq_parent.__propsets__=tuple(result)
+ aq_parent(self).__propsets__=tuple(result)
## DM: deletion support
def isDeletable(self,name):
@@ -743,7 +744,7 @@
pre=pre+'/'
r=[]
- for d in self.aq_parent.manage_options:
+ for d in aq_parent(self).manage_options:
r.append({'label': d['label'], 'action': pre+d['action']})
return r
Modified: Zope/trunk/lib/python/OFS/SimpleItem.py
===================================================================
--- Zope/trunk/lib/python/OFS/SimpleItem.py 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/OFS/SimpleItem.py 2008-04-26 17:46:46 UTC (rev 85767)
@@ -205,14 +205,16 @@
if match is not None:
error_message=error_value
- if client is None: client=self
- if not REQUEST: REQUEST=self.aq_acquire('REQUEST')
+ if client is None:
+ client = self
+ if not REQUEST:
+ REQUEST = aq_acquire(self, 'REQUEST')
try:
if hasattr(client, 'standard_error_message'):
s=getattr(client, 'standard_error_message')
else:
- client = client.aq_parent
+ client = aq_parent(client)
s=getattr(client, 'standard_error_message')
kwargs = {'error_type': error_type,
'error_value': error_value,
@@ -329,7 +331,7 @@
raise ValueError('FTP List not supported on acquired objects')
if not hasattr(ob,'aq_parent'):
break
- ob=ob.aq_parent
+ ob = aq_parent(ob)
stat=marshal.loads(self.manage_FTPstat(REQUEST))
id = self.getId()
Modified: Zope/trunk/lib/python/OFS/Traversable.py
===================================================================
--- Zope/trunk/lib/python/OFS/Traversable.py 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/OFS/Traversable.py 2008-04-26 17:46:46 UTC (rev 85767)
@@ -22,7 +22,8 @@
from AccessControl import getSecurityManager
from AccessControl import Unauthorized
from AccessControl.ZopeGuards import guarded_getattr
-from Acquisition import Acquired, aq_inner, aq_parent, aq_base
+from Acquisition import Acquired, aq_inner, aq_parent, aq_acquire, aq_base
+from Acquisition.interfaces import IAcquirer
from zExceptions import NotFound
from ZODB.POSException import ConflictError
from OFS.interfaces import ITraversable
@@ -64,7 +65,7 @@
spp = self.getPhysicalPath()
try:
- toUrl = self.REQUEST.physicalPathToURL
+ toUrl = aq_acquire(self, 'REQUEST').physicalPathToURL
except AttributeError:
return path2url(spp[1:])
return toUrl(spp)
@@ -78,7 +79,7 @@
"""
spp = self.getPhysicalPath()
try:
- toUrl = self.REQUEST.physicalPathToURL
+ toUrl = aq_acquire(self, 'REQUEST').physicalPathToURL
except AttributeError:
return path2url(spp) or '/'
return toUrl(spp, relative=1) or '/'
@@ -93,7 +94,7 @@
"""
spp = self.getPhysicalPath()
try:
- toVirt = self.REQUEST.physicalPathToVirtualPath
+ toVirt = aq_acquire(self, 'REQUEST').physicalPathToVirtualPath
except AttributeError:
return path2url(spp[1:])
return path2url(toVirt(spp))
@@ -191,7 +192,9 @@
ns, nm = nsParse(name)
try:
next = namespaceLookup(
- ns, nm, obj, self.REQUEST).__of__(obj)
+ ns, nm, obj, aq_acquire(self, 'REQUEST'))
+ if IAcquirer.providedBy(next):
+ next = next.__of__(obj)
if restricted and not validate(
obj, obj, name, next):
raise Unauthorized(name)
@@ -256,11 +259,10 @@
except (AttributeError, NotFound, KeyError), e:
# Try to look for a view
- next = queryMultiAdapter((obj, self.REQUEST),
+ next = queryMultiAdapter((obj, aq_acquire(self, 'REQUEST')),
Interface, name)
if next is not None:
- next = next.__of__(obj)
if restricted and not validate(obj, obj, name, next):
raise Unauthorized(name)
elif bobo_traverse is not None:
Modified: Zope/trunk/lib/python/OFS/ZDOM.py
===================================================================
--- Zope/trunk/lib/python/OFS/ZDOM.py 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/OFS/ZDOM.py 2008-04-26 17:46:46 UTC (rev 85767)
@@ -16,6 +16,8 @@
All standard Zope objects support DOM to a limited extent.
"""
import Acquisition
+from Acquisition import aq_base
+from Acquisition import aq_parent
from Globals import InitializeClass
from AccessControl import ClassSecurityInfo
from AccessControl.Permissions import access_contents_information
@@ -149,7 +151,7 @@
When this is a document this is None"""
node = self
if hasattr(node, 'aq_parent'):
- node = self.aq_parent
+ node = aq_parent(self)
return node.getOwnerDocument()
return node
@@ -198,7 +200,7 @@
This is a convenience attribute that allows direct access to
the child node that is the root element of the document.
"""
- return self.aq_parent
+ return aq_parent(self)
# Node Methods
# ------------
@@ -219,17 +221,17 @@
def getChildNodes(self):
"""Returns a NodeList that contains all children of this node.
If there are no children, this is a empty NodeList"""
- return NodeList([self.aq_parent])
+ return NodeList([aq_parent(self)])
def getFirstChild(self):
"""The first child of this node. If there is no such node
this returns None."""
- return self.aq_parent
+ return aq_parent(self)
def getLastChild(self):
"""The last child of this node. If there is no such node
this returns None."""
- return self.aq_parent
+ return aq_parent(self)
def hasChildNodes(self):
"""Returns true if the node has any children, false
@@ -324,7 +326,7 @@
"""The node immediately preceding this node. If
there is no such node, this returns None."""
if hasattr(self, 'aq_parent'):
- parent = self.aq_parent
+ parent = aq_parent(self)
ids=list(parent.objectIds())
id=self.id
if type(id) is not type(''): id=id()
@@ -338,7 +340,7 @@
"""The node immediately preceding this node. If
there is no such node, this returns None."""
if hasattr(self, 'aq_parent'):
- parent = self.aq_parent
+ parent = aq_parent(self)
ids=list(parent.objectIds())
id=self.id
if type(id) is not type(''): id=id()
@@ -432,7 +434,7 @@
def getAttribute(self, name):
"""Retrieves an attribute value by name."""
- if name=='title' and hasattr(self.aq_base, 'title'):
+ if name=='title' and hasattr(aq_base(self), 'title'):
return self.title
return ''
Modified: Zope/trunk/lib/python/Products/Five/bbb.py
===================================================================
--- Zope/trunk/lib/python/Products/Five/bbb.py 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/Products/Five/bbb.py 2008-04-26 17:46:46 UTC (rev 85767)
@@ -19,10 +19,32 @@
from zope.component.interfaces import ComponentLookupError
from zope.app.publisher.browser import getDefaultViewName
-import zExceptions
-import Products.Five.security
-from Products.Five import fivemethod
+import Acquisition
+
+class AcquisitionBBB(object):
+ """Emulate a class implementing Acquisition.interfaces.IAcquirer and
+ IAcquisitionWrapper.
+ """
+
+ def __of__(self, context):
+ # Technically this isn't in line with the way Acquisition's
+ # __of__ works. With Acquisition, you get a wrapper around
+ # the original object and only that wrapper's parent is the
+ # new context.
+ return self
+
+ aq_self = aq_inner = aq_base = property(lambda self: self)
+ aq_chain = property(Acquisition.aq_chain)
+ aq_parent = property(Acquisition.aq_parent)
+
+ def aq_acquire(self, *args, **kw):
+ return Acquisition.aq_acquire(self, *args, **kw)
+
+ def aq_inContextOf(self, *args, **kw):
+ return Acquisition.aq_inContextOf(self, *args, **kw)
+
+
class IBrowserDefault(Interface):
"""Provide a hook for deciding about the default view for an object"""
Modified: Zope/trunk/lib/python/Products/Five/browser/__init__.py
===================================================================
--- Zope/trunk/lib/python/Products/Five/browser/__init__.py 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/Products/Five/browser/__init__.py 2008-04-26 17:46:46 UTC (rev 85767)
@@ -18,8 +18,24 @@
import Acquisition
import zope.publisher.browser
-class BrowserView(Acquisition.Explicit, zope.publisher.browser.BrowserView):
- """Five browser view
+from Products.Five.bbb import AcquisitionBBB
- Mixes in explicit acquisition so that security can be acquired for
- views"""
+
+class BrowserView(zope.publisher.browser.BrowserView, AcquisitionBBB):
+
+ # Use an explicit __init__ to work around problems with magically inserted
+ # super classes when using BrowserView as a base for viewlets.
+ def __init__(self, context, request):
+ zope.publisher.browser.BrowserView.__init__(self, context, request)
+
+ # Classes which are still based on Acquisition and access
+ # self.context in a method need to call aq_inner on it, or get a
+ # funky aq_chain. We do this here for BBB friendly purposes.
+
+ def __getParent(self):
+ return getattr(self, '_parent', Acquisition.aq_inner(self.context))
+
+ def __setParent(self, parent):
+ self._parent = parent
+
+ aq_parent = __parent__ = property(__getParent, __setParent)
Modified: Zope/trunk/lib/python/Products/Five/browser/absoluteurl.py
===================================================================
--- Zope/trunk/lib/python/Products/Five/browser/absoluteurl.py 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/Products/Five/browser/absoluteurl.py 2008-04-26 17:46:46 UTC (rev 85767)
@@ -15,26 +15,89 @@
$Id$
"""
+import urllib
from Acquisition import aq_inner, aq_parent
from OFS.interfaces import ITraversable
from zope.interface import implements
from zope.component import getMultiAdapter
from zope.traversing.browser.interfaces import IAbsoluteURL
+from zope.traversing.browser.absoluteurl import _insufficientContext, _safe
from Products.Five.browser import BrowserView
class AbsoluteURL(BrowserView):
- """An adapter for Zope3-style absolute_url using Zope2 methods
+ """An absolute_url adapter for generic objects in Zope 2 that
+ aren't OFS.Traversable (e.g. views, resources, etc.).
- (original: zope.traversing.browser.absoluteurl)
+ This is very close to the generic implementation from
+ zope.traversing.browser, but the Zope 2 request doesn't support
+ all the methods that it uses yet.
"""
implements(IAbsoluteURL)
- def __init__(self, context, request):
- self.context, self.request = context, request
+ def __unicode__(self):
+ return urllib.unquote(self.__str__()).decode('utf-8')
def __str__(self):
+ context = self.context
+ request = self.request
+
+ container = aq_parent(context)
+ if container is None:
+ raise TypeError(_insufficientContext)
+
+ url = str(getMultiAdapter((container, request), name='absolute_url'))
+ name = self._getContextName(context)
+ if name is None:
+ raise TypeError(_insufficientContext)
+
+ if name:
+ url += '/' + urllib.quote(name.encode('utf-8'), _safe)
+
+ return url
+
+ __call__ = __str__
+
+ def _getContextName(self, context):
+ if getattr(context, 'getId', None) is not None:
+ return context.getId()
+ getattr(context, '__name__', None)
+
+ def breadcrumbs(self):
+ context = self.context
+ request = self.request
+
+ # We do this here do maintain the rule that we must be wrapped
+ container = aq_parent(context)
+ if container is None:
+ raise TypeError(_insufficientContext)
+
+ base = tuple(getMultiAdapter((container, request),
+ name='absolute_url').breadcrumbs())
+
+ name = self._getContextName(context)
+ if name is None:
+ raise TypeError(_insufficientContext)
+
+ if name:
+ base += ({'name': name,
+ 'url': ("%s/%s" % (base[-1]['url'],
+ urllib.quote(name.encode('utf-8'),
+ _safe)))
+ }, )
+
+ return base
+
+class OFSTraversableAbsoluteURL(BrowserView):
+ """An absolute_url adapter for OFS.Traversable subclasses
+ """
+ implements(IAbsoluteURL)
+
+ def __unicode__(self):
+ return urllib.unquote(self.__str__()).decode('utf-8')
+
+ def __str__(self):
context = aq_inner(self.context)
return context.absolute_url()
@@ -47,10 +110,10 @@
name = context.getId()
- if container is None or self._isVirtualHostRoot() \
- or not ITraversable.providedBy(container):
- return (
- {'name': name, 'url': context.absolute_url()},)
+ if (container is None
+ or self._isVirtualHostRoot()
+ or not ITraversable.providedBy(container)):
+ return ({'name': name, 'url': context.absolute_url()},)
view = getMultiAdapter((container, request), IAbsoluteURL)
base = tuple(view.breadcrumbs())
@@ -66,15 +129,9 @@
context = aq_inner(self.context)
return context.restrictedTraverse(virtualrootpath) == context
-class SiteAbsoluteURL(AbsoluteURL):
- """An adapter for Zope3-style absolute_url using Zope2 methods
-
- This one is just used to stop breadcrumbs from crumbing up
- to the Zope root.
-
- (original: zope.traversing.browser.absoluteurl)
+class RootAbsoluteURL(OFSTraversableAbsoluteURL):
+ """An absolute_url adapter for the root object (OFS.Application)
"""
-
def breadcrumbs(self):
context = self.context
request = self.request
Modified: Zope/trunk/lib/python/Products/Five/browser/adding.py
===================================================================
--- Zope/trunk/lib/python/Products/Five/browser/adding.py 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/Products/Five/browser/adding.py 2008-04-26 17:46:46 UTC (rev 85767)
@@ -23,6 +23,8 @@
__docformat__ = 'restructuredtext'
+from warnings import warn
+
from zope.component import getMultiAdapter
from zope.component import getUtility
from zope.component import queryMultiAdapter
@@ -41,15 +43,13 @@
from zope.app.container.interfaces import IContainerNamesContainer
from zope.app.publisher.browser.menu import getMenu
-from Acquisition import Implicit
from zExceptions import BadRequest
from OFS.SimpleItem import SimpleItem
from Products.Five import BrowserView
from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
-
-class Adding(Implicit, BrowserView):
+class BasicAdding(BrowserView):
implements(IAdding, IPublishTraverse)
def add(self, content):
@@ -78,7 +78,7 @@
# Invoke the name chooser even when we have a
# name. It'll do useful things with it like converting
# the incoming unicode to an ASCII string.
- name = chooser.chooseName(name, content)
+ name = chooser.chooseName(name, container)
content.id = name
container._setObject(name, content)
@@ -92,12 +92,18 @@
# XXX this is definitely not right for all or even most uses
# of Five, but can be overridden by an AddView subclass, using
# the class attribute of a zcml:addform directive
- return absoluteURL(self.context, self.request) + '/manage_main'
+ return str(getMultiAdapter((self.context, self.request),
+ name=u"absolute_url")) + '/manage_main'
# set in BrowserView.__init__
request = None
context = None
+ def renderAddButton(self):
+ warn("The renderAddButton method is deprecated, use nameAllowed",
+ DeprecationWarning, 2)
+
+
def publishTraverse(self, request, name):
"""See zope.publisher.interfaces.IPublishTraverse"""
if '=' in name:
@@ -119,7 +125,7 @@
factory = queryUtility(IFactory, name)
if factory is None:
- return super(Adding, self).publishTraverse(request, name)
+ return super(BasicAdding, self).publishTraverse(request, name)
return factory
@@ -135,10 +141,11 @@
else:
view_name = type_name
- if queryMultiAdapter((self, self.request),
- name=view_name) is not None:
+ if (queryMultiAdapter((self, self.request), name=view_name)
+ is not None):
url = "%s/%s=%s" % (
- absoluteURL(self, self.request), type_name, id)
+ getMultiAdapter((self, self.request), name=u"absolute_url"),
+ type_name, id)
self.request.response.redirect(url)
return
@@ -153,10 +160,16 @@
self.add(content)
self.request.response.redirect(self.nextURL())
+ def namesAccepted(self):
+ return not IContainerNamesContainer.providedBy(self.context)
+
def nameAllowed(self):
"""Return whether names can be input by the user."""
return not IContainerNamesContainer.providedBy(self.context)
+
+class Adding(BasicAdding):
+
menu_id = None
index = ViewPageTemplateFile("adding.pt")
Modified: Zope/trunk/lib/python/Products/Five/browser/configure.zcml
===================================================================
--- Zope/trunk/lib/python/Products/Five/browser/configure.zcml 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/Products/Five/browser/configure.zcml 2008-04-26 17:46:46 UTC (rev 85767)
@@ -39,21 +39,37 @@
/>
<browser:page
- for="zope.traversing.interfaces.IContainmentRoot"
+ for="OFS.interfaces.ITraversable"
name="absolute_url"
- class=".absoluteurl.SiteAbsoluteURL"
+ class=".absoluteurl.OFSTraversableAbsoluteURL"
permission="zope.Public"
allowed_interface="zope.traversing.browser.interfaces.IAbsoluteURL"
/>
<view
- for="zope.traversing.interfaces.IContainmentRoot"
- factory=".absoluteurl.SiteAbsoluteURL"
+ for="OFS.interfaces.ITraversable"
+ factory=".absoluteurl.OFSTraversableAbsoluteURL"
type="zope.publisher.interfaces.http.IHTTPRequest"
permission="zope.Public"
provides="zope.traversing.browser.interfaces.IAbsoluteURL"
/>
+ <browser:page
+ for="OFS.interfaces.IApplication"
+ name="absolute_url"
+ class=".absoluteurl.RootAbsoluteURL"
+ permission="zope.Public"
+ allowed_interface="zope.traversing.browser.interfaces.IAbsoluteURL"
+ />
+
+ <view
+ for="OFS.interfaces.IApplication"
+ factory=".absoluteurl.RootAbsoluteURL"
+ type="zope.publisher.interfaces.http.IHTTPRequest"
+ permission="zope.Public"
+ provides="zope.traversing.browser.interfaces.IAbsoluteURL"
+ />
+
<browser:view
for="OFS.interfaces.IObjectManager"
name="+"
Modified: Zope/trunk/lib/python/Products/Five/browser/metaconfigure.py
===================================================================
--- Zope/trunk/lib/python/Products/Five/browser/metaconfigure.py 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/Products/Five/browser/metaconfigure.py 2008-04-26 17:46:46 UTC (rev 85767)
@@ -29,17 +29,17 @@
from zope.publisher.interfaces.browser import IBrowserRequest, \
IDefaultBrowserLayer
-from zope.app.publisher.browser.viewmeta import pages as zope_app_pages
-from zope.app.publisher.browser.viewmeta import view as zope_app_view
-from zope.app.publisher.browser.viewmeta import providesCallable, \
- _handle_menu, _handle_for
+import zope.app.publisher.browser.viewmeta
+import zope.app.pagetemplate.simpleviewclass
+from zope.app.publisher.browser.viewmeta import (providesCallable,
+ _handle_menu, _handle_for)
from Products.Five.browser import BrowserView
from Products.Five.browser.resource import FileResourceFactory
from Products.Five.browser.resource import ImageResourceFactory
from Products.Five.browser.resource import PageTemplateResourceFactory
from Products.Five.browser.resource import DirectoryResourceFactory
-from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile
+from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
from Products.Five.metaclass import makeClass
from Products.Five.security import getSecurityInfo, protectClass, protectName
from Products.Five.security import CheckerPrivateId
@@ -159,7 +159,7 @@
args = (new_class,)
)
-class pages(zope_app_pages):
+class pages(zope.app.publisher.browser.viewmeta.pages):
def page(self, _context, name, attribute='__call__', template=None,
menu=None, title=None):
@@ -172,7 +172,7 @@
# view (named view with pages)
-class view(zope_app_view):
+class view(zope.app.publisher.browser.viewmeta.view):
def __call__(self):
(_context, name, for_, permission, layer, class_,
@@ -185,7 +185,7 @@
for pname, attribute, template in self.pages:
if template:
- cdict[pname] = ZopeTwoPageTemplateFile(template)
+ cdict[pname] = ViewPageTemplateFile(template)
if attribute and attribute != name:
cdict[attribute] = cdict[pname]
else:
@@ -209,9 +209,9 @@
view = component.queryMultiAdapter((self, request), name=name,
default=None)
if view is not None:
- return view.__of__(self)
+ return view
- m = class_.publishTraverse.__get__(self).__of__(self)
+ m = class_.publishTraverse.__get__(self)
return m(request, name)
else:
@@ -223,7 +223,7 @@
view = component.queryMultiAdapter((self, request), name=name,
default=None)
if view is not None:
- return view.__of__(self)
+ return view
raise NotFoundError(self, name, request)
@@ -389,39 +389,29 @@
args = (new_class,)
)
-#
-# mixin classes / class factories
-#
+class ViewMixinForAttributes(BrowserView,
+ zope.app.publisher.browser.viewmeta.simple):
-class ViewMixinForAttributes(BrowserView):
+ # For some reason, the 'simple' baseclass doesn't implement this
+ # mandatory method (see https://bugs.launchpad.net/zope3/+bug/129296)
+ def browserDefault(self, request):
+ return getattr(self, self.__page_attribute__), ()
- # we have an attribute that we can simply tell ZPublisher to go to
- def __browser_default__(self, request):
- return self, (self.__page_attribute__,)
+ # __call__ should have the same signature as the original method
+ @property
+ def __call__(self):
+ return getattr(self, self.__page_attribute__)
- # this is technically not needed because ZPublisher finds our
- # attribute through __browser_default__; but we also want to be
- # able to call pages from python modules, PythonScripts or ZPT
- __call__ = property(lambda self: getattr(self, self.__page_attribute__))
+class ViewMixinForTemplates(BrowserView,
+ zope.app.pagetemplate.simpleviewclass.simple):
+ pass
-class ViewMixinForTemplates(BrowserView):
-
- # short cut to get to macros more easily
- def __getitem__(self, name):
- if name == 'macros':
- return self.index.macros
- return self.index.macros[name]
-
- # make the template publishable
- def __call__(self, *args, **kw):
- return self.index(self, *args, **kw)
-
def makeClassForTemplate(filename, globals=None, used_for=None,
bases=(), cdict=None, name=u''):
# XXX needs to deal with security from the bases?
if cdict is None:
cdict = {}
- cdict.update({'index': ZopeTwoPageTemplateFile(filename, globals),
+ cdict.update({'index': ViewPageTemplateFile(filename, globals),
'__name__': name})
bases += (ViewMixinForTemplates,)
class_ = makeClass("SimpleViewClass from %s" % filename, bases, cdict)
Modified: Zope/trunk/lib/python/Products/Five/browser/pagetemplatefile.py
===================================================================
--- Zope/trunk/lib/python/Products/Five/browser/pagetemplatefile.py 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/Products/Five/browser/pagetemplatefile.py 2008-04-26 17:46:46 UTC (rev 85767)
@@ -15,85 +15,84 @@
$Id$
"""
-import os, sys
+from os.path import basename
+from zope.app.pagetemplate import viewpagetemplatefile
-from Acquisition import aq_inner
-from Globals import package_home
-from Products.PageTemplates.PageTemplateFile import PageTemplateFile
+from Acquisition import aq_get
+from AccessControl import getSecurityManager
from Products.PageTemplates.Expressions import SecureModuleImporter
from Products.PageTemplates.Expressions import createTrustedZopeEngine
-from zope.app.pagetemplate.viewpagetemplatefile import ViewMapper
+from Products.Five.bbb import AcquisitionBBB
+
_engine = createTrustedZopeEngine()
def getEngine():
return _engine
-class ZopeTwoPageTemplateFile(PageTemplateFile):
- """A strange hybrid between Zope 2 and Zope 3 page template.
-
- Uses Zope 2's engine, but with security disabled and with some
- initialization and API from Zope 3.
+class ViewPageTemplateFile(viewpagetemplatefile.ViewPageTemplateFile):
+ """Page Template used as class variable of views defined as Python classes.
"""
- def __init__(self, filename, _prefix=None, content_type=None):
- # XXX doesn't use content_type yet
+ def getId(self):
+ return basename(self.filename)
- self.ZBindings_edit(self._default_bindings)
+ id = property(getId)
- path = self.get_path_from_prefix(_prefix)
- self.filename = os.path.join(path, filename)
- if not os.path.isfile(self.filename):
- raise ValueError("No such file", self.filename)
+ def __call__(self, __instance, *args, **keywords):
+ instance = __instance
+ namespace = self.pt_getContext(
+ request=instance.request,
+ instance=instance, args=args, options=keywords)
+ debug_flags = instance.request.debug
+ s = self.pt_render(
+ namespace,
+ showtal=getattr(debug_flags, 'showTAL', 0),
+ sourceAnnotations=getattr(debug_flags, 'sourceAnnotations', 0),
+ )
+ response = instance.request.response
+ if not response.getHeader("Content-Type"):
+ response.setHeader("Content-Type", self.content_type)
+ return s
- basepath, ext = os.path.splitext(self.filename)
- self.__name__ = os.path.basename(basepath)
+ def pt_getEngine(self):
+ return getEngine()
- super(PageTemplateFile, self).__init__(self.filename, _prefix)
+ def pt_getContext(self, instance, request, **kw):
+ context = super(ViewPageTemplateFile, self).pt_getContext(
+ instance, request, **kw)
- def get_path_from_prefix(self, _prefix):
- if isinstance(_prefix, str):
- path = _prefix
- else:
- if _prefix is None:
- _prefix = sys._getframe(2).f_globals
- path = package_home(_prefix)
- return path
+ # get the root
+ obj = context['context']
+ root = None
+ meth = aq_get(obj, 'getPhysicalRoot', None)
+ if meth is not None:
+ root = meth()
- def pt_getEngine(self):
- return getEngine()
+ context.update(here=context['context'],
+ # philiKON thinks container should be the view,
+ # but BBB is more important than aesthetics.
+ container=context['context'],
+ root=root,
+ modules=SecureModuleImporter,
+ traverse_subpath=[], # BBB, never really worked
+ user = getSecurityManager().getUser()
+ )
+ return context
- def pt_getContext(self):
- try:
- root = self.getPhysicalRoot()
- except AttributeError:
- try:
- root = self.context.getPhysicalRoot()
- except AttributeError:
- root = None
+ def __get__(self, instance, type):
+ return BoundPageTemplate(self, instance)
- # Even if the context isn't a view (when would that be exaclty?),
- # there shouldn't be any dange in applying a view, because it
- # won't be used. However assuming that a lack of getPhysicalRoot
- # implies a missing view causes problems.
- view = self._getContext()
- here = aq_inner(self.context)
+# When a view's template is accessed e.g. as template.view, a
+# BoundPageTemplate object is retured. For BBB reasons, it needs to
+# support the aq_* methods and attributes known from Acquisition. For
+# that it also needs to be locatable thru __parent__.
- request = getattr(root, 'REQUEST', None)
- c = {'template': self,
- 'here': here,
- 'context': here,
- 'container': here,
- 'nothing': None,
- 'options': {},
- 'root': root,
- 'request': request,
- 'modules': SecureModuleImporter,
- }
- if view is not None:
- c['view'] = view
- c['views'] = ViewMapper(here, request)
+class BoundPageTemplate(viewpagetemplatefile.BoundPageTemplate,
+ AcquisitionBBB):
- return c
+ __parent__ = property(lambda self: self.im_self)
-ViewPageTemplateFile = ZopeTwoPageTemplateFile
+
+# BBB
+ZopeTwoPageTemplateFile = ViewPageTemplateFile
Modified: Zope/trunk/lib/python/Products/Five/browser/providerexpression.py
===================================================================
--- Zope/trunk/lib/python/Products/Five/browser/providerexpression.py 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/Products/Five/browser/providerexpression.py 2008-04-26 17:46:46 UTC (rev 85767)
@@ -12,21 +12,23 @@
#
##############################################################################
"""Provider expression.
-
-$Id$
"""
+import zope.event
+import zope.interface
import zope.component
-from zope.contentprovider import interfaces as cp_interfaces
-from zope.contentprovider.tales import addTALNamespaceData
-from zope.interface import implements
-from zope.tales.expressions import StringExpr
-class Z2ProviderExpression(StringExpr):
- """Create a custom provider expression which overrides __call__ to
- acquisition wrap the provider so that security lookups can be done."""
+from zope.tales import expressions
+from zope.contentprovider import interfaces, tales
+from zope.location.interfaces import ILocation
- implements(cp_interfaces.ITALESProviderExpression)
+from Acquisition.interfaces import IAcquirer
+class Z2ProviderExpression(expressions.StringExpr):
+ zope.interface.implements(interfaces.ITALESProviderExpression)
+
+ # This is mostly a copy of
+ # zope.contentprovider.tales.TALESProviderExpression's __call__
+ # method.
def __call__(self, econtext):
name = super(Z2ProviderExpression, self).__call__(econtext)
context = econtext.vars['context']
@@ -35,19 +37,26 @@
# Try to look up the provider.
provider = zope.component.queryMultiAdapter(
- (context, request, view), cp_interfaces.IContentProvider, name)
+ (context, request, view), interfaces.IContentProvider, name)
# Provide a useful error message, if the provider was not found.
if provider is None:
- raise cp_interfaces.ContentProviderLookupError(name)
+ raise interfaces.ContentProviderLookupError(name)
- if getattr(provider, '__of__', None) is not None:
+ # add the __name__ attribute if it implements ILocation
+ if ILocation.providedBy(provider):
+ provider.__name__ = name
+
+ # ATTN: This is where we are different from
+ # TALESProviderExpression: We support Acquisition wrapping.
+ if IAcquirer.providedBy(provider):
provider = provider.__of__(context)
# Insert the data gotten from the context
- addTALNamespaceData(provider, econtext)
+ tales.addTALNamespaceData(provider, econtext)
# Stage 1: Do the state update.
+ zope.event.notify(interfaces.BeforeUpdateEvent(provider, request))
provider.update()
# Stage 2: Render the HTML content.
Modified: Zope/trunk/lib/python/Products/Five/browser/resource.py
===================================================================
--- Zope/trunk/lib/python/Products/Five/browser/resource.py 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/Products/Five/browser/resource.py 2008-04-26 17:46:46 UTC (rev 85767)
@@ -18,112 +18,61 @@
import os
import urllib
-import Acquisition
-from OFS.Traversable import Traversable as OFSTraversable
-from zope.app.publisher.browser.resources import empty
-from zope.app.publisher.fileresource import File, Image
-from zope.app.publisher.pagetemplateresource import PageTemplate
from zope.interface import implements
from zope.component import getMultiAdapter
-from zope.component.interfaces import IResource
-from zope.datetime import time as timeFromDateTimeString
-from zope.traversing.browser.interfaces import IAbsoluteURL
+from zope.traversing.browser import absoluteURL
+from zope.publisher.interfaces import NotFound
+from zope.publisher.interfaces.browser import IBrowserPublisher
+from zope.app.publisher.browser import fileresource, directoryresource
+from zope.app.publisher.fileresource import File, Image
+from zope.app.publisher.pagetemplateresource import PageTemplate
from Products.Five.browser import BrowserView
-_marker = []
-class Resource(Acquisition.Explicit):
- """A publishable resource
- """
- implements(IResource)
+_marker = object()
- def __init__(self, request):
- self.request = request
+class Resource(object):
+ """A mixin that changes the URL-rendering of resources (__call__).
+ In Zope 3, resource URLs are of the form
+ nearest_site/@@/resource_name. Since Zope 2 didn't have support
+ for sites from the beginning of the Five integration, resource
+ URLs in Zope 2 are of the form context/++resource++resource_name.
+
+ TODO It would be good if that could be changed in the long term,
+ thus making this mixin (and probably the other classes in this
+ module) obsolete.
+ """
def __call__(self):
name = self.__name__
container = self.__parent__
- # TODO Zope 3 uses site = getSite() instead of container here
- # and the @@ resource access view
- url = str(getMultiAdapter((container, self.request), IAbsoluteURL))
- url = urllib.unquote(url)
+ url = urllib.unquote(absoluteURL(container, self.request))
if not isinstance(container, DirectoryResource):
name = '++resource++%s' % name
return "%s/%s" % (url, name)
-class PageTemplateResource(BrowserView, Resource):
- #implements(IBrowserPublisher)
+class PageTemplateResource(Resource, BrowserView):
+ implements(IBrowserPublisher)
- def __browser_default__(self, request):
- return self, ('render',)
+ def browserDefault(self, request):
+ return self.render, ()
+ def publishTraverse(self, request, name):
+ raise NotFound(self, name, request)
+
def render(self):
"""Rendered content"""
# ZPublisher might have called setBody with an incorrect URL
# we definitely don't want that if we are plain html
- self.request.RESPONSE.setBase(None)
+ self.request.response.setBase(None)
pt = self.context
return pt(self.request)
-class FileResource(BrowserView, Resource):
- """A publishable file-based resource"""
- #implements(IBrowserPublisher)
+class FileResource(Resource, fileresource.FileResource):
+ pass
- def __browser_default__(self, request):
- return self, (request.REQUEST_METHOD,)
-
- def GET(self):
- """Default content"""
- file = self.context
- request = self.request
- response = request.response
-
- # HTTP If-Modified-Since header handling. This is duplicated
- # from OFS.Image.Image - it really should be consolidated
- # somewhere...
- header = request.environ.get('If-Modified-Since', None)
- if header is not None:
- header = header.split(';')[0]
- # Some proxies seem to send invalid date strings for this
- # header. If the date string is not valid, we ignore it
- # rather than raise an error to be generally consistent
- # with common servers such as Apache (which can usually
- # understand the screwy date string as a lucky side effect
- # of the way they parse it).
- try: mod_since=long(timeFromDateTimeString(header))
- except: mod_since=None
- if mod_since is not None:
- if getattr(file, 'lmt', None):
- last_mod = long(file.lmt)
- else:
- last_mod = long(0)
- if last_mod > 0 and last_mod <= mod_since:
- response.setStatus(304)
- return ''
-
- response.setHeader('Content-Type', file.content_type)
- response.setHeader('Last-Modified', file.lmh)
-
- # Cache for one day
- response.setHeader('Cache-Control', 'public,max-age=86400')
- f = open(file.path, 'rb')
- data = f.read()
- f.close()
-
- return data
-
- def HEAD(self):
- file = self.context
- response = self.request.response
- response = self.request.response
- response.setHeader('Content-Type', file.content_type)
- response.setHeader('Last-Modified', file.lmh)
- # Cache for one day
- response.setHeader('Cache-Control', 'public,max-age=86400')
- return ''
-
class ResourceFactory:
factory = None
@@ -173,8 +122,7 @@
self.path = path
self.__name__ = name
-class DirectoryResource(BrowserView, Resource, OFSTraversable):
- #implements(IBrowserPublisher)
+class DirectoryResource(Resource, directoryresource.DirectoryResource):
resource_factories = {
'gif': ImageResourceFactory,
@@ -187,28 +135,12 @@
default_factory = FileResourceFactory
- def __init__(self, context, request):
- BrowserView.__init__(self, context, request)
- # OFSTraversable.absolute_url() assumes self.REQUEST being
- # accessible:
- self.REQUEST = request
-
def getId(self):
name = self.__name__
if not name.startswith('++resource++'):
name = '++resource++%s' % self.__name__
return name
- def __browser_default__(self, request):
- '''See interface IBrowserPublisher'''
- return empty, ()
-
- def __getitem__(self, name):
- res = self.get(name, None)
- if res is None:
- raise KeyError, name
- return res
-
def get(self, name, default=_marker):
path = self.context.path
filename = os.path.join(path, name)
@@ -229,11 +161,7 @@
resource = factory(name, filename)(self.request)
resource.__name__ = name
resource.__parent__ = self
- # XXX __of__ wrapping is usually done on traversal.
- # However, we don't want to subclass Traversable (or do we?)
- # The right thing should probably be a specific (and very simple)
- # traverser that does __getitem__ and __of__.
- return resource.__of__(self)
+ return resource
class DirectoryResourceFactory(ResourceFactory):
Copied: Zope/trunk/lib/python/Products/Five/browser/tests/aqlegacy.py (from rev 85463, Zope/branches/philikon-aq/lib/python/Products/Five/browser/tests/aqlegacy.py)
===================================================================
--- Zope/trunk/lib/python/Products/Five/browser/tests/aqlegacy.py (rev 0)
+++ Zope/trunk/lib/python/Products/Five/browser/tests/aqlegacy.py 2008-04-26 17:46:46 UTC (rev 85767)
@@ -0,0 +1,147 @@
+##############################################################################
+#
+# Copyright (c) 2007 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Legacy browser view tests.
+
+Here we nake sure that legacy implementations of views (e.g. those
+which mix-in one of the Acquisition base classes without knowing
+better) still work.
+"""
+import Acquisition
+import OFS.SimpleItem
+
+from zope.interface import implements
+from zope.traversing.interfaces import ITraversable
+from zope.contentprovider.interfaces import IContentProvider
+from Products.Five import BrowserView
+from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
+
+class LegacyAttributes(BrowserView):
+ """Make sure that those old aq_* attributes on Five BrowserViews
+ still work, in particular aq_chain, even though BrowserView may
+ not be an Acquisition-decendant class anymore...
+ """
+
+ def __call__(self):
+ return repr([obj for obj in self.aq_chain])
+
+class ExplicitLegacyAttributes(Acquisition.Explicit):
+ """Make sure that those old aq_* attributes work on browser views
+ that only inherit from Explicit as well."""
+
+ def __call__(self):
+ return repr([obj for obj in self.aq_chain])
+
+class LegacyTemplate(BrowserView):
+
+ template = ViewPageTemplateFile('falcon.pt')
+
+ def __call__(self):
+ return self.template()
+
+class LegacyTemplateTwo(BrowserView):
+
+ def __init__(self, context, request):
+ self.__parent__ = context
+ self.context = context
+ self.request = request
+ self.template = ViewPageTemplateFile('falcon.pt')
+
+ def __call__(self):
+ return self.template()
+
+class Explicit(Acquisition.Explicit):
+
+ def render(self):
+ return 'Explicit'
+
+class ExplicitWithTemplate(Acquisition.Explicit):
+
+ template = ViewPageTemplateFile('falcon.pt')
+
+class Implicit(Acquisition.Implicit):
+
+ def render(self):
+ return 'Implicit'
+
+class ImplicitWithTemplate(Acquisition.Implicit):
+
+ template = ViewPageTemplateFile('falcon.pt')
+
+
+class ExplicitContentProvider(Acquisition.Explicit):
+ implements(IContentProvider)
+
+ def __init__(self, context, request, view):
+ self.context = context
+ self.request = request
+ self.view = view
+ # Normally, a content provider should set __parent__ to view
+ # or context. This one doesn't do this on purpose to ensure
+ # it works without.
+
+ def update(self):
+ # Make sure that the content provider is acquisition wrapped.
+ assert self.aq_parent == self.context
+ assert self.aq_base == self
+
+ def render(self):
+ return 'Content provider inheriting from Explicit'
+
+class ExplicitViewlet(Acquisition.Explicit):
+
+ def __init__(self, context, request, view, manager):
+ self.context = context
+ self.request = request
+
+ def update(self):
+ # Make sure that the viewlet has the legacy attributes and
+ # they point to the right objects.
+ assert self.aq_parent == self.context
+ assert self.aq_base == self
+
+ def render(self):
+ return 'Viewlet inheriting from Explicit'
+
+class BrowserViewViewlet(BrowserView):
+
+ def __init__(self, context, request, view, manager):
+ # This is the tricky bit. super(...).__init__ wouldn't
+ # necessarily have to resolve to BrowserView.__init__ because
+ # <browser:viewlet /> generates classes on the fly with a
+ # mix-in base class...
+ super(BrowserViewViewlet, self).__init__(context, request)
+ self.view = view
+ self.manager = manager
+
+ def update(self):
+ pass
+
+ def render(self):
+ return 'BrowserView viewlet'
+
+
+class LegacyNamespace(object):
+ implements(ITraversable)
+
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+
+ def traverse(self, name, ignored):
+ return LegacyNamespaceObject(name)
+
+class LegacyNamespaceObject(OFS.SimpleItem.SimpleItem):
+
+ def __init__(self, name):
+ self.id = name
Copied: Zope/trunk/lib/python/Products/Five/browser/tests/aqlegacy.zcml (from rev 85463, Zope/branches/philikon-aq/lib/python/Products/Five/browser/tests/aqlegacy.zcml)
===================================================================
--- Zope/trunk/lib/python/Products/Five/browser/tests/aqlegacy.zcml (rev 0)
+++ Zope/trunk/lib/python/Products/Five/browser/tests/aqlegacy.zcml 2008-04-26 17:46:46 UTC (rev 85767)
@@ -0,0 +1,142 @@
+<configure xmlns="http://namespaces.zope.org/zope"
+ xmlns:browser="http://namespaces.zope.org/browser">
+
+ <browser:page
+ for="*"
+ name="attributes"
+ class=".aqlegacy.LegacyAttributes"
+ permission="zope.Public"
+ />
+
+ <browser:page
+ for="*"
+ name="explicitattributes"
+ class=".aqlegacy.ExplicitLegacyAttributes"
+ permission="zope.Public"
+ />
+
+ <browser:page
+ for="*"
+ name="template"
+ class=".aqlegacy.LegacyTemplate"
+ permission="zope.Public"
+ />
+
+ <browser:page
+ for="*"
+ name="template_two"
+ class=".aqlegacy.LegacyTemplateTwo"
+ permission="zope.Public"
+ />
+
+ <browser:page
+ for="*"
+ name="explicit"
+ class=".aqlegacy.Explicit"
+ attribute="render"
+ permission="zope.Public"
+ />
+
+ <browser:page
+ for="*"
+ name="explicit_zcmltemplate"
+ class=".aqlegacy.Explicit"
+ template="falcon.pt"
+ permission="zope.Public"
+ />
+
+ <browser:page
+ for="*"
+ name="explicit_template"
+ class=".aqlegacy.ExplicitWithTemplate"
+ attribute="template"
+ permission="zope.Public"
+ />
+
+ <browser:page
+ for="*"
+ name="implicit"
+ class=".aqlegacy.Implicit"
+ attribute="render"
+ permission="zope.Public"
+ />
+
+ <browser:page
+ for="*"
+ name="implicit_template"
+ class=".aqlegacy.ImplicitWithTemplate"
+ attribute="template"
+ permission="zope.Public"
+ />
+
+ <browser:page
+ for="*"
+ name="implicit_zcmltemplate"
+ class=".aqlegacy.Implicit"
+ template="falcon.pt"
+ permission="zope.Public"
+ />
+
+ <!-- Content providers and viewlets -->
+
+ <adapter
+ for="* * *"
+ provides="zope.contentprovider.interfaces.IContentProvider"
+ factory=".aqlegacy.ExplicitContentProvider"
+ name="aqlegacyprovider"
+ />
+
+ <browser:page
+ for="*"
+ name="aqlegacyprovider"
+ template="legacyprovider.pt"
+ permission="zope.Public"
+ />
+
+ <browser:viewletManager
+ name="aqlegacymanager"
+ permission="zope.Public"
+ />
+
+ <browser:viewlet
+ for="*"
+ class=".aqlegacy.ExplicitViewlet"
+ name="explicit"
+ permission="zope.Public"
+ />
+
+ <browser:viewlet
+ for="*"
+ class=".aqlegacy.BrowserViewViewlet"
+ name="browserview"
+ permission="zope.Public"
+ />
+
+ <browser:page
+ for="*"
+ name="aqlegacymanager"
+ template="legacymanager.pt"
+ permission="zope.Public"
+ />
+
+ <!-- Namespace traversal -->
+
+ <adapter
+ for="*"
+ factory=".aqlegacy.LegacyNamespace"
+ name="aqlegacy"
+ />
+ <adapter
+ for="* *"
+ factory=".aqlegacy.LegacyNamespace"
+ name="aqlegacy"
+ />
+
+ <browser:page
+ for=".aqlegacy.LegacyNamespaceObject"
+ name="index.html"
+ template="falcon.pt"
+ permission="zope.Public"
+ />
+
+</configure>
\ No newline at end of file
Copied: Zope/trunk/lib/python/Products/Five/browser/tests/aqlegacy_ftest.txt (from rev 85463, Zope/branches/philikon-aq/lib/python/Products/Five/browser/tests/aqlegacy_ftest.txt)
===================================================================
--- Zope/trunk/lib/python/Products/Five/browser/tests/aqlegacy_ftest.txt (rev 0)
+++ Zope/trunk/lib/python/Products/Five/browser/tests/aqlegacy_ftest.txt 2008-04-26 17:46:46 UTC (rev 85767)
@@ -0,0 +1,204 @@
+Testing legacy browser views
+============================
+
+This test tests publishing aspects of browser pages. Let's register
+some:
+
+ >>> import Products.Five.browser.tests
+ >>> from Products.Five import zcml
+ >>> zcml.load_config("configure.zcml", Products.Five)
+ >>> zcml.load_config('aqlegacy.zcml', package=Products.Five.browser.tests)
+
+ >>> from Products.Five.testbrowser import Browser
+ >>> browser = Browser()
+ >>> browser.handleErrors = False
+
+Acquisition API legacy on BrowserView
+-------------------------------------
+
+Let's make sure that accessing those old aq_* properties on browser
+views still works (the printed output is the aq_chain of the view):
+
+ >>> browser.open('http://localhost/test_folder_1_/attributes')
+ >>> print browser.contents
+ [<Products.Five.metaclass.LegacyAttributes object at ...>,
+ <Folder at /test_folder_1_>,
+ <Application at >,
+ <ZPublisher.BaseRequest.RequestContainer object at ...>]
+
+The same goes for browser views that just mix in Acquisition.Explicit:
+
+ >>> browser.open('http://localhost/test_folder_1_/explicitattributes')
+ >>> print browser.contents
+ [<Products.Five.metaclass.ExplicitLegacyAttributes object at ...>,
+ <Folder at /test_folder_1_>,
+ <Application at >,
+ <ZPublisher.BaseRequest.RequestContainer object at ...>]
+
+Let's do some more manual tests with the view object. But first we
+must get it:
+
+ >>> from zope.component import getMultiAdapter
+ >>> from zope.publisher.browser import TestRequest
+ >>> request = TestRequest()
+ >>> view = getMultiAdapter((self.folder, request), name='attributes')
+
+Let's check for the various aq_* attributes:
+
+ >>> view.aq_parent == self.folder
+ True
+ >>> view.aq_inner == view
+ True
+ >>> view.aq_base == view
+ True
+ >>> view.aq_self == view
+ True
+
+Let's try to acquire something from the root folder:
+
+ >>> button = view.aq_acquire('ZopeAttributionButton')
+ >>> print button()
+ <a href="http://www.zope.org/Credits" target="_top"><img src="http://nohost/p_/ZopeButton" width="115" height="50" border="0" alt="Powered by Zope" /></a>
+
+Let's check that we're in the right context:
+
+ >>> view.aq_inContextOf(self.folder)
+ 1
+ >>> view.aq_inContextOf(self.app)
+ 1
+ >>> view.aq_inContextOf(object())
+ 0
+
+Views also still support the __of__ protocol, at least pro forma:
+
+ >>> view == view.__of__(self.app)
+ True
+
+Acquisition API legacy on a browser view's template
+---------------------------------------------------
+
+A view's template will also support the various aq_* attributes:
+
+ >>> view = getMultiAdapter((self.folder, request), name='template')
+ >>> template = view.template
+
+ >>> template.aq_parent == view
+ True
+ >>> template.aq_inner == template
+ True
+ >>> template.aq_base == template
+ True
+ >>> template.aq_self == template
+ True
+
+ >>> button = template.aq_acquire('ZopeAttributionButton')
+ >>> print button()
+ <a href="http://www.zope.org/Credits" target="_top"><img src="http://nohost/p_/ZopeButton" width="115" height="50" border="0" alt="Powered by Zope" /></a>
+
+
+Mixing in Acquisition.{Ex|Im}plicit
+-----------------------------------
+
+Let's make sure that mixing in Acquisition.Explicit or Implicit won't
+mess up your views (even though you should never have done it in the
+first place...):
+
+ >>> browser.open('http://localhost/test_folder_1_/explicit')
+ >>> print browser.contents
+ Explicit
+
+ >>> browser.open('http://localhost/test_folder_1_/explicit_zcmltemplate')
+ >>> print browser.contents
+ <p>The falcon has taken flight</p>
+
+ >>> browser.open('http://localhost/test_folder_1_/explicit_template')
+ >>> print browser.contents
+ <p>The falcon has taken flight</p>
+
+ >>> browser.open('http://localhost/test_folder_1_/implicit')
+ >>> print browser.contents
+ Implicit
+
+ >>> browser.open('http://localhost/test_folder_1_/implicit_template')
+ >>> print browser.contents
+ <p>The falcon has taken flight</p>
+
+ >>> browser.open('http://localhost/test_folder_1_/implicit_zcmltemplate')
+ >>> print browser.contents
+ <p>The falcon has taken flight</p>
+
+
+Testing legacy content providers and viewlets
+=============================================
+
+ >>> browser.open('http://localhost/test_folder_1_/aqlegacyprovider')
+ >>> print browser.contents
+ <p>Content provider inheriting from Explicit</p>
+
+ >>> browser.open('http://localhost/test_folder_1_/aqlegacymanager')
+ >>> print browser.contents
+ <p>BrowserView viewlet
+ Viewlet inheriting from Explicit</p>
+
+
+Testing namespace traversal
+===========================
+
+Namespace traversal can turn up objects during traversal without
+attribute access. That means they might not be wrapped by default.
+Here we make sure that they are wrapped and that things like the
+request can be acquired.
+
+First let's try ``restrictedTraverse()``:
+
+ >>> foo = self.folder.restrictedTraverse('++aqlegacy++foo')
+ >>> import Acquisition
+ >>> Acquisition.aq_acquire(foo, 'REQUEST')
+ <HTTPRequest, URL=http://nohost>
+
+Now let's try URL traversal:
+
+ >>> browser.open('http://localhost/test_folder_1_/++aqlegacy++foo/index.html')
+ >>> print browser.contents
+ <p>The falcon has taken flight</p>
+
+
+Testing keyword arguments
+=========================
+
+ViewPageTemplateFile's take arbitrary keyword arguments:
+
+ >>> view = getMultiAdapter((self.folder, request), name='template')
+ >>> template = view.template
+ >>> print template(foo=1, bar=2)
+ <p>The falcon has taken flight</p>
+
+Passing in an argument called instance was supported by the old Five version
+of ViewPageTemplateFile, so we still need to support it.
+
+In the zope.app.pagetemplate version, the first required argument is called
+instance, though.
+
+ >>> print template(instance='allowed')
+ <p>The falcon has taken flight</p>
+
+
+No arguments required
+=====================
+
+ViewPageTemplateFile's require no arguments, but you can only use them as
+class variables:
+
+ >>> view = getMultiAdapter((self.folder, request), name='template_two')
+ >>> print view()
+ Traceback (most recent call last):
+ ...
+ TypeError: __call__() takes at least 2 arguments (1 given)
+
+
+
+Clean up
+--------
+
+ >>> from zope.app.testing.placelesssetup import tearDown
+ >>> tearDown()
Copied: Zope/trunk/lib/python/Products/Five/browser/tests/legacymanager.pt (from rev 85463, Zope/branches/philikon-aq/lib/python/Products/Five/browser/tests/legacymanager.pt)
===================================================================
--- Zope/trunk/lib/python/Products/Five/browser/tests/legacymanager.pt (rev 0)
+++ Zope/trunk/lib/python/Products/Five/browser/tests/legacymanager.pt 2008-04-26 17:46:46 UTC (rev 85767)
@@ -0,0 +1 @@
+<p tal:content="provider:aqlegacymanager" />
Copied: Zope/trunk/lib/python/Products/Five/browser/tests/legacyprovider.pt (from rev 85463, Zope/branches/philikon-aq/lib/python/Products/Five/browser/tests/legacyprovider.pt)
===================================================================
--- Zope/trunk/lib/python/Products/Five/browser/tests/legacyprovider.pt (rev 0)
+++ Zope/trunk/lib/python/Products/Five/browser/tests/legacyprovider.pt 2008-04-26 17:46:46 UTC (rev 85767)
@@ -0,0 +1 @@
+<p tal:content="provider:aqlegacyprovider" />
Modified: Zope/trunk/lib/python/Products/Five/browser/tests/pages.py
===================================================================
--- Zope/trunk/lib/python/Products/Five/browser/tests/pages.py 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/Products/Five/browser/tests/pages.py 2008-04-26 17:46:46 UTC (rev 85767)
@@ -16,6 +16,7 @@
$Id$
"""
from Products.Five import BrowserView
+from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
class SimpleView(BrowserView):
"""More docstring. Please Zope"""
@@ -39,6 +40,10 @@
def __call__(self):
return u"I was __call__()'ed"
+class CallTemplate(BrowserView):
+
+ __call__ = ViewPageTemplateFile('falcon.pt')
+
class CallableNoDocstring:
def __call__(self):
@@ -58,7 +63,7 @@
class NewStyleClass(object):
"""
- This is a testclass to verify that new style classes are ignored
+ This is a testclass to verify that new style classes work
in browser:page
"""
Modified: Zope/trunk/lib/python/Products/Five/browser/tests/pages.txt
===================================================================
--- Zope/trunk/lib/python/Products/Five/browser/tests/pages.txt 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/Products/Five/browser/tests/pages.txt 2008-04-26 17:46:46 UTC (rev 85767)
@@ -157,7 +157,7 @@
>>> print view()
View is a view: True
Context is testoid: True
- Contaxt.aq_parent is test_folder_1_: True
+ Context.aq_parent is test_folder_1_: True
Container is context: True
Here is context: True
Nothing is None: True
@@ -215,37 +215,43 @@
It's protecting the object with the permission, and not the attribute,
so we get ('',) instead of ('eagle',):
- >>> getattr(view, '__ac_permissions__')
+ >>> view.__ac_permissions__
(('View management screens', ('',)),)
-Wrap into an acquisition so that imPermissionRole objects can be
-evaluated. __roles__ is a imPermissionRole object:
+The view's __roles__ attribute can be evaluated correctly:
- >>> view = view.__of__(self.folder.testoid)
- >>> view_roles = getattr(view, '__roles__', None)
- >>> view_roles
+(We have to use aq_acquire here instead of a simple getattr. The
+reason is that __roles__ actually is an object that expects being
+called through the __of__ protocol upon which it renders the roles
+tuple. aq_acquire will trigger this for us. This isn't a problem,
+really, because AccessControl ends up using aq_acquire anyway, so it
+Just Works.)
+
+ >>> from Acquisition import aq_acquire
+ >>> aq_acquire(view, '__roles__')
('Manager',)
Check to see if view's context properly acquires its true
parent
>>> from Acquisition import aq_parent, aq_base, aq_inner
- >>> context = getattr(view, 'context')
+ >>> context = view.context
Check the wrapper type
>>> from Acquisition import ImplicitAcquisitionWrapper
>>> type(context) == ImplicitAcquisitionWrapper
True
-
-The acquired parent is the view. This isn't
-usually what you want.
- >>> aq_parent(context) == view
- True
+The parent of the view is the view's context:
-To get what you usually want, do this
+ >>> view.__parent__ == view.context
+ True
+ >>> aq_parent(view) == view.context
+ True
+The direct parent of the context is
+
>>> context.aq_inner.aq_parent
<Folder at /test_folder_1_>
Modified: Zope/trunk/lib/python/Products/Five/browser/tests/pages.zcml
===================================================================
--- Zope/trunk/lib/python/Products/Five/browser/tests/pages.zcml 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/Products/Five/browser/tests/pages.zcml 2008-04-26 17:46:46 UTC (rev 85767)
@@ -161,6 +161,13 @@
permission="zope2.Public"
/>
+ <browser:page
+ for="Products.Five.tests.testing.simplecontent.ISimpleContent"
+ class=".pages.CallTemplate"
+ name="calltemplate.html"
+ permission="zope2.Public"
+ />
+
<!-- pages from methods/functions/callables that don't have docstrings -->
<browser:pages
for="Products.Five.tests.testing.simplecontent.ISimpleContent"
Modified: Zope/trunk/lib/python/Products/Five/browser/tests/pages_ftest.txt
===================================================================
--- Zope/trunk/lib/python/Products/Five/browser/tests/pages_ftest.txt 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/Products/Five/browser/tests/pages_ftest.txt 2008-04-26 17:46:46 UTC (rev 85767)
@@ -144,7 +144,15 @@
...
I was __call__()'ed
+or a __call__ object that's callable, such as a ViewPageTemplateFile:
+ >>> print http(r'''
+ ... GET /test_folder_1_/testoid/calltemplate.html HTTP/1.1
+ ... ''')
+ HTTP/1.1 200 OK
+ ...
+ <p>The falcon has taken flight</p>
+
Clean up
--------
Modified: Zope/trunk/lib/python/Products/Five/browser/tests/provider.txt
===================================================================
--- Zope/trunk/lib/python/Products/Five/browser/tests/provider.txt 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/Products/Five/browser/tests/provider.txt 2008-04-26 17:46:46 UTC (rev 85767)
@@ -188,18 +188,15 @@
</body>
</html>
-Now we test a provider using a PageTemplateFile to render itself. It must
-inherit from an Acquisition base class so that the template can use Zope 2
-security mechanisms:
+Now we test a provider using a PageTemplateFile to render itself:
>>> import os, tempfile
>>> temp_dir = tempfile.mkdtemp()
>>> dynTemplate = os.path.join(temp_dir, 'dynamic_template.pt')
>>> open(dynTemplate, 'w').write(
... 'A simple template: <tal:simple replace="python:view.my_property" />')
- >>> from Acquisition import Explicit
>>> from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile
- >>> class TemplateProvider(Explicit):
+ >>> class TemplateProvider(object):
... zope.component.adapts(zope.interface.Interface,
... browser.IDefaultBrowserLayer,
... zope.interface.Interface)
Modified: Zope/trunk/lib/python/Products/Five/browser/tests/resource_ftest.txt
===================================================================
--- Zope/trunk/lib/python/Products/Five/browser/tests/resource_ftest.txt 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/Products/Five/browser/tests/resource_ftest.txt 2008-04-26 17:46:46 UTC (rev 85767)
@@ -33,6 +33,15 @@
HTTP/1.1 200 OK
...
+Image resources can't be traversed further:
+
+ >>> print http(r'''
+ ... GET /test_folder_1_/testoid/++resource++pattern.png/more HTTP/1.1
+ ... Authorization: Basic manager:r00t
+ ... ''')
+ HTTP/1.1 404 Not Found
+ ...
+
File resource
~~~~~~~~~~~~~
@@ -43,6 +52,15 @@
HTTP/1.1 200 OK
...
+File resources can't be traversed further:
+
+ >>> print http(r'''
+ ... GET /test_folder_1_/testoid/++resource++style.css/more HTTP/1.1
+ ... Authorization: Basic manager:r00t
+ ... ''')
+ HTTP/1.1 404 Not Found
+ ...
+
Template resource
~~~~~~~~~~~~~~~~~
@@ -53,6 +71,15 @@
HTTP/1.1 200 OK
...
+Template resources can't be traversed further:
+
+ >>> print http(r'''
+ ... GET /test_folder_1_/testoid/++resource++cockatiel.html/more HTTP/1.1
+ ... Authorization: Basic manager:r00t
+ ... ''')
+ HTTP/1.1 404 Not Found
+ ...
+
Resource directory
~~~~~~~~~~~~~~~~~~
@@ -77,19 +104,7 @@
This is a resource in a subdirectory of a normal resource to test traversal.
<BLANKLINE>
-
-We also can traverse into sub-directories:
-
>>> print http(r'''
- ... GET /test_folder_1_/testoid/++resource++fivetest_resources/resource_subdir/resource.txt HTTP/1.1
- ... Authorization: Basic manager:r00t
- ... ''')
- HTTP/1.1 200 OK
- ...
- This is a resource in a subdirectory of a normal resource to test traversal.
- <BLANKLINE>
-
- >>> print http(r'''
... GET /test_folder_1_/testoid/++resource++fivetest_resources/resource_subdir/resource.html HTTP/1.1
... Authorization: Basic manager:r00t
... ''')
@@ -105,6 +120,13 @@
</html>
<BLANKLINE>
+ >>> print http(r'''
+ ... GET /test_folder_1_/testoid/++resource++fivetest_resources/resource_subdir/not-existant HTTP/1.1
+ ... Authorization: Basic manager:r00t
+ ... ''')
+ HTTP/1.1 404 Not Found
+ ...
+
Clean up
--------
Modified: Zope/trunk/lib/python/Products/Five/browser/tests/template_variables.pt
===================================================================
--- Zope/trunk/lib/python/Products/Five/browser/tests/template_variables.pt 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/Products/Five/browser/tests/template_variables.pt 2008-04-26 17:46:46 UTC (rev 85767)
@@ -1,7 +1,7 @@
View is a view: <tal:block
content="python:hasattr(view,'context') and hasattr(view, 'request')" />
Context is testoid: <tal:block content="python:context.id == 'testoid'" />
-Contaxt.aq_parent is test_folder_1_: <tal:block
+Context.aq_parent is test_folder_1_: <tal:block
content="python:context.aq_parent.id =='test_folder_1_'" />
Container is context: <tal:block content="python:container is context" />
Here is context: <tal:block content="python:here is context"/>
@@ -10,7 +10,7 @@
Root is the application: <tal:block
replace="python:repr(root).find('Application') != -1" />
Template is a template: <tal:block
- replace="python:repr(template.aq_base).startswith('<ZopeTwoPageTemplateFile')" />
+ replace="python:'ViewPageTemplateFile' in repr(template)" />
Traverse_subpath exists and is empty: <tal:block
replace="python:traverse_subpath == []" />
Request is a request: <tal:block
Modified: Zope/trunk/lib/python/Products/Five/browser/tests/test_absoluteurl.py
===================================================================
--- Zope/trunk/lib/python/Products/Five/browser/tests/test_absoluteurl.py 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/Products/Five/browser/tests/test_absoluteurl.py 2008-04-26 17:46:46 UTC (rev 85767)
@@ -51,12 +51,11 @@
This test assures and demonstrates that the absolute url stops
traversing through an object's parents when it has reached the
- root object. In Zope 3 this is marked with the IContainmentRoot
- interface:
+ root object.
- >>> from zope.interface import directlyProvides, providedBy
- >>> from zope.traversing.interfaces import IContainmentRoot
- >>> directlyProvides(self.folder, IContainmentRoot)
+ >>> from zope.interface import alsoProvides, noLongerProvides
+ >>> from OFS.interfaces import IApplication
+ >>> alsoProvides(self.folder, IApplication)
>>> for crumb in view.breadcrumbs():
... info = crumb.items()
@@ -65,8 +64,7 @@
[('name', 'test_folder_1_'), ('url', 'http://nohost/test_folder_1_')]
[('name', 'testoid'), ('url', 'http://nohost/test_folder_1_/testoid')]
- >>> directlyProvides(self.folder,
- ... providedBy(self.folder) - IContainmentRoot)
+ >>> noLongerProvides(self.folder, IApplication)
The absolute url view is obviously not affected by virtual hosting:
Modified: Zope/trunk/lib/python/Products/Five/browser/tests/test_pages.py
===================================================================
--- Zope/trunk/lib/python/Products/Five/browser/tests/test_pages.py 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/Products/Five/browser/tests/test_pages.py 2008-04-26 17:46:46 UTC (rev 85767)
@@ -19,47 +19,6 @@
if __name__ == '__main__':
execfile(os.path.join(sys.path[0], 'framework.py'))
-def test_ViewAcquisitionWrapping():
- """
- >>> import Products.Five.browser.tests
- >>> from Products.Five import zcml
- >>> zcml.load_config("configure.zcml", Products.Five)
- >>> zcml.load_config('pages.zcml', package=Products.Five.browser.tests)
-
- >>> from Products.Five.tests.testing import simplecontent as sc
- >>> sc.manage_addSimpleContent(self.folder, 'testoid', 'Testoid')
- >>> uf = self.folder.acl_users
- >>> uf._doAddUser('manager', 'r00t', ['Manager'], [])
- >>> self.login('manager')
-
- >>> view = self.folder.unrestrictedTraverse('testoid/eagle.txt')
- >>> view is not None
- True
- >>> from Products.Five.browser.tests.pages import SimpleView
- >>> isinstance(view, SimpleView)
- True
- >>> view()
- u'The eagle has landed'
-
- This sucks, but we know it
-
- >>> from Acquisition import aq_parent, aq_base
- >>> aq_parent(view.context) is view
- True
-
- This is the right way to get the context parent
-
- >>> view.context.aq_inner.aq_parent is not view
- True
- >>> view.context.aq_inner.aq_parent is self.folder
- True
-
- Clean up:
-
- >>> from zope.app.testing.placelesssetup import tearDown
- >>> tearDown()
- """
-
def test_view_with_unwrapped_context():
"""
It may be desirable when writing tests for views themselves to
@@ -118,7 +77,9 @@
ZopeDocTestSuite(),
ZopeDocFileSuite('pages.txt', package='Products.Five.browser.tests'),
FunctionalDocFileSuite('pages_ftest.txt',
- package='Products.Five.browser.tests')
+ package='Products.Five.browser.tests'),
+ FunctionalDocFileSuite('aqlegacy_ftest.txt',
+ package='Products.Five.browser.tests'),
))
return suite
Modified: Zope/trunk/lib/python/Products/Five/component/__init__.py
===================================================================
--- Zope/trunk/lib/python/Products/Five/component/__init__.py 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/Products/Five/component/__init__.py 2008-04-26 17:46:46 UTC (rev 85767)
@@ -36,7 +36,7 @@
"""Find a site by walking up the object hierarchy, supporting both
the ``ILocation`` API and Zope 2 Acquisition."""
while obj is not None and not iface.providedBy(obj):
- obj = getattr(obj, '__parent__', aq_parent(aq_inner(obj)))
+ obj = aq_parent(aq_inner(obj))
return obj
@zope.component.adapter(zope.interface.Interface)
Modified: Zope/trunk/lib/python/Products/Five/doc/manual.txt
===================================================================
--- Zope/trunk/lib/python/Products/Five/doc/manual.txt 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/Products/Five/doc/manual.txt 2008-04-26 17:46:46 UTC (rev 85767)
@@ -268,13 +268,6 @@
attribute. Typically this comes from a base class, such as
``BrowserView``.
-* This also means they need to be part of the Zope 2 acquisition
- system, as this is a requirement for Zope 2 security to
- function. The ``BrowserView`` base class, available from
- ``Products.Five``, already inherits from ``Acquisition.Explicit`` to
- make this be the case. Acquisition is explicit so no attributes can
- be acquired by accident.
-
An example of a simple view::
from Products.Five import BrowserView
Modified: Zope/trunk/lib/python/Products/Five/form/objectwidget.py
===================================================================
--- Zope/trunk/lib/python/Products/Five/form/objectwidget.py 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/Products/Five/form/objectwidget.py 2008-04-26 17:46:46 UTC (rev 85767)
@@ -14,31 +14,26 @@
"""Five-compatible version of ObjectWidget
This is needed because ObjectWidget uses ViewPageTemplateFile whose
-macro definition is unfortunately incompatible with
-ZopeTwoPageTemplateFile. So this subclass uses
-ZopeTwoPageTemplateFile for the template that renders the widget's
-sub-editform. Acquisition has to be mixed in to provide the
-ZopeTwoPageTemplateFile with the proper acquisition context.
+macro definition is unfortunately incompatible with ZopeTwoPageTemplateFile.
+So this subclass uses ZopeTwoPageTemplateFile for the template that renders
+the widget's sub-editform.
$Id$
"""
-import Acquisition
import zope.app.form.browser.objectwidget
from AccessControl import ClassSecurityInfo
from Globals import InitializeClass as initializeClass
-from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile
+from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
-class ObjectWidgetView(Acquisition.Explicit,
- zope.app.form.browser.objectwidget.ObjectWidgetView):
+class ObjectWidgetView(zope.app.form.browser.objectwidget.ObjectWidgetView):
security = ClassSecurityInfo()
security.declareObjectPublic()
- template = ZopeTwoPageTemplateFile('objectwidget.pt')
+ template = ViewPageTemplateFile('objectwidget.pt')
initializeClass(ObjectWidgetView)
-class ObjectWidgetClass(Acquisition.Explicit,
- zope.app.form.browser.objectwidget.ObjectWidget):
+class ObjectWidgetClass(zope.app.form.browser.objectwidget.ObjectWidget):
def __init__(self, context, request, factory, **kw):
super(ObjectWidgetClass, self).__init__(context, request, factory, **kw)
@@ -58,8 +53,4 @@
val = self.context.schema[name].default
self.getSubWidget(name).setRenderedValue(val)
-def ObjectWidget(context, request, factory, **kw):
- """Return an ObjectWidget suitable in the Five environment, with
- right acquisition context"""
- return ObjectWidgetClass(context, request, factory, **kw
- ).__of__(context.context)
+ObjectWidget = ObjectWidgetClass
Modified: Zope/trunk/lib/python/Products/Five/formlib/formbase.py
===================================================================
--- Zope/trunk/lib/python/Products/Five/formlib/formbase.py 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/Products/Five/formlib/formbase.py 2008-04-26 17:46:46 UTC (rev 85767)
@@ -18,7 +18,6 @@
import os.path
from datetime import datetime
-import Acquisition
import zope.event
import zope.formlib
@@ -36,7 +35,7 @@
_SUBPAGEFORM_PATH = os.path.join(_FORMLIB_DIR, 'subpageform.pt')
-class FiveFormlibMixin(Acquisition.Explicit):
+class FiveFormlibMixin(object):
# Overrides the formlib.form.FormBase.template attributes implemented
# using NamedTemplates. NamedTemplates using ViewPageTemplateFile (like
Modified: Zope/trunk/lib/python/Products/Five/i18n.py
===================================================================
--- Zope/trunk/lib/python/Products/Five/i18n.py 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/Products/Five/i18n.py 2008-04-26 17:46:46 UTC (rev 85767)
@@ -15,7 +15,7 @@
$Id$
"""
-from Acquisition import aq_acquire
+from Acquisition import aq_get
from zope.interface import implements
from zope.i18n.interfaces import IFallbackTranslationDomainFactory
from zope.i18n.interfaces import ITranslationDomain
@@ -23,6 +23,7 @@
from zope.i18n.negotiator import normalize_lang
from zope.component import queryUtility
from zope.i18nmessageid import Message
+from zope.publisher.interfaces.browser import IBrowserRequest
class FiveTranslationService:
@@ -60,8 +61,11 @@
# in Zope3, context is adapted to IUserPreferredLanguages,
# which means context should be the request in this case.
+ # Do not attempt to acquire REQUEST from the context, when we already
+ # got a request as the context
if context is not None:
- context = aq_acquire(context, 'REQUEST', None)
+ if not IBrowserRequest.providedBy(context):
+ context = aq_get(context, 'REQUEST', None)
return util.translate(msgid, mapping=mapping, context=context,
target_language=target_language, default=default)
Modified: Zope/trunk/lib/python/Products/Five/site/tests/test_utility.py
===================================================================
--- Zope/trunk/lib/python/Products/Five/site/tests/test_utility.py 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/Products/Five/site/tests/test_utility.py 2008-04-26 17:46:46 UTC (rev 85767)
@@ -283,17 +283,17 @@
# let's see if we can acquire something all the way from the
# root (Application) object; we need to be careful to choose
# something that's only available from the root object
- from Acquisition import aq_acquire
+ from Acquisition import aq_get
dummy = zapi.getUtility(IDummyUtility)
- acquired = aq_acquire(dummy, 'ZopeAttributionButton', None)
+ acquired = aq_get(dummy, 'ZopeAttributionButton', None)
self.failUnless(acquired is not None)
name, dummy = zapi.getUtilitiesFor(IDummyUtility).next()
- acquired = aq_acquire(dummy, 'ZopeAttributionButton', None)
+ acquired = aq_get(dummy, 'ZopeAttributionButton', None)
self.failUnless(acquired is not None)
dummy = zapi.getAllUtilitiesRegisteredFor(IDummyUtility).next()
- acquired = aq_acquire(dummy, 'ZopeAttributionButton', None)
+ acquired = aq_get(dummy, 'ZopeAttributionButton', None)
self.failUnless(acquired is not None)
def test_getNextUtility(self):
Modified: Zope/trunk/lib/python/Products/Five/viewlet/README.txt
===================================================================
--- Zope/trunk/lib/python/Products/Five/viewlet/README.txt 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/Products/Five/viewlet/README.txt 2008-04-26 17:46:46 UTC (rev 85767)
@@ -89,8 +89,7 @@
>>> from zope.publisher.interfaces.browser import IDefaultBrowserLayer
>>> from zope.publisher.interfaces.browser import IBrowserView
- >>> from Acquisition import Explicit
- >>> class WeatherBox(Explicit):
+ >>> class WeatherBox(object):
... zope.interface.implements(interfaces.IViewlet)
...
... def __init__(self, context, request, view, manager):
@@ -109,7 +108,7 @@
... IBrowserView, ILeftColumn),
... interfaces.IViewlet, name='weather')
- >>> class SportBox(Explicit):
+ >>> class SportBox(object):
... zope.interface.implements(interfaces.IViewlet)
...
... def __init__(self, context, request, view, manager):
@@ -311,7 +310,7 @@
>>> foo.render()
Traceback (most recent call last):
...
- AttributeError: bar
+ AttributeError: 'FooViewlet' object has no attribute 'bar'
To create simple template-based viewlets you can use the
``SimpleViewletClass()`` function. This function is very similar to its view
@@ -365,7 +364,7 @@
The viewlet will look up the resource it was given and tries to produce the
absolute URL for it:
- >>> class JSResource(Explicit):
+ >>> class JSResource(object):
... def __init__(self, request):
... self.request = request
...
@@ -381,7 +380,7 @@
The same works for the CSS resource viewlet:
- >>> class CSSResource(Explicit):
+ >>> class CSSResource(object):
... def __init__(self, request):
... self.request = request
...
@@ -487,7 +486,7 @@
>>> shownColumns = []
- >>> class ContentsViewletManager(Explicit):
+ >>> class ContentsViewletManager(object):
... zope.interface.implements(interfaces.IViewletManager)
... index = None
...
@@ -562,7 +561,7 @@
Now let's create a first viewlet for the manager...
- >>> class NameViewlet(Explicit):
+ >>> class NameViewlet(object):
...
... def __init__(self, context, request, view, manager):
... self.__parent__ = view
@@ -635,7 +634,7 @@
Let's now write a second viewlet that will display the size of the object for
us:
- >>> class SizeViewlet(Explicit):
+ >>> class SizeViewlet(object):
...
... def __init__(self, context, request, view, manager):
... self.__parent__ = view
Modified: Zope/trunk/lib/python/Products/Five/viewlet/directives.txt
===================================================================
--- Zope/trunk/lib/python/Products/Five/viewlet/directives.txt 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/Products/Five/viewlet/directives.txt 2008-04-26 17:46:46 UTC (rev 85767)
@@ -130,8 +130,6 @@
<Products.Five.viewlet.manager.<ViewletManager providing ILeftColumn> object ...>
>>> ILeftColumn.providedBy(manager)
True
- >>> manager.template.meta_type
- 'Page Template (File)'
>>> manager.update()
>>> print manager.render().strip()
<div class="column">
@@ -164,8 +162,6 @@
<class 'Products.Five.viewlet.manager.ViewletManagerBase'>)
>>> ILeftColumn.providedBy(manager)
True
- >>> manager.template.meta_type
- 'Page Template (File)'
>>> manager.update()
>>> print manager.render().strip()
<div class="column">
Modified: Zope/trunk/lib/python/Products/Five/viewlet/manager.py
===================================================================
--- Zope/trunk/lib/python/Products/Five/viewlet/manager.py 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/Products/Five/viewlet/manager.py 2008-04-26 17:46:46 UTC (rev 85767)
@@ -15,7 +15,7 @@
$Id$
"""
-import Acquisition
+from Acquisition import aq_base
from AccessControl.ZopeGuards import guarded_hasattr
import zope.interface
import zope.security
@@ -24,9 +24,7 @@
from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile
-aq_base = Acquisition.aq_base
-
-class ViewletManagerBase(origManagerBase, Acquisition.Explicit):
+class ViewletManagerBase(origManagerBase):
"""A base class for Viewlet managers to work in Zope2"""
def __getitem__(self, name):
@@ -41,9 +39,6 @@
raise zope.component.interfaces.ComponentLookupError(
'No provider with name `%s` found.' %name)
- # Wrap the viewlet for security lookups
- viewlet = viewlet.__of__(viewlet.context)
-
# If the viewlet cannot be accessed, then raise an
# unauthorized error
if not guarded_hasattr(viewlet, 'render'):
@@ -65,7 +60,6 @@
# the object has a real context from which to determine owner
# security.
for name, viewlet in viewlets:
- viewlet = viewlet.__of__(viewlet.context)
if guarded_hasattr(viewlet, 'render'):
results.append((name, viewlet))
return results
Modified: Zope/trunk/lib/python/Products/Five/viewlet/viewlet.py
===================================================================
--- Zope/trunk/lib/python/Products/Five/viewlet/viewlet.py 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/Products/Five/viewlet/viewlet.py 2008-04-26 17:46:46 UTC (rev 85767)
@@ -16,19 +16,18 @@
$Id$
"""
import os
-from Acquisition import Explicit
-from zope.viewlet import viewlet as orig_viewlet
+import zope.viewlet.viewlet
+from Products.Five.bbb import AcquisitionBBB
+from Products.Five.browser.pagetemplatefile import ViewPageTemplateFile
-from Products.Five.browser.pagetemplatefile import ZopeTwoPageTemplateFile
-
-# We add Acquisition to all the base classes to enable security machinery
-class ViewletBase(orig_viewlet.ViewletBase, Explicit):
+class ViewletBase(zope.viewlet.viewlet.ViewletBase, AcquisitionBBB):
pass
-class SimpleAttributeViewlet(orig_viewlet.SimpleAttributeViewlet, Explicit):
+class SimpleAttributeViewlet(zope.viewlet.viewlet.SimpleAttributeViewlet,
+ AcquisitionBBB):
pass
-class simple(orig_viewlet.simple):
+class simple(zope.viewlet.viewlet.simple):
# We need to ensure that the proper __init__ is called.
__init__ = ViewletBase.__init__.im_func
@@ -41,7 +40,7 @@
# Create the base class hierarchy
bases += (simple, ViewletBase)
- attrs = {'index' : ZopeTwoPageTemplateFile(template),
+ attrs = {'index' : ViewPageTemplateFile(template),
'__name__' : name}
if attributes:
attrs.update(attributes)
@@ -52,7 +51,7 @@
return class_
-class ResourceViewletBase(orig_viewlet.ResourceViewletBase, Explicit):
+class ResourceViewletBase(zope.viewlet.viewlet.ResourceViewletBase):
pass
def JavaScriptViewlet(path):
@@ -61,13 +60,13 @@
klass = type('JavaScriptViewlet',
(ResourceViewletBase, ViewletBase),
- {'index': ZopeTwoPageTemplateFile(src),
+ {'index': ViewPageTemplateFile(src),
'_path': path})
return klass
-class CSSResourceViewletBase(orig_viewlet.CSSResourceViewletBase):
+class CSSResourceViewletBase(zope.viewlet.viewlet.CSSResourceViewletBase):
pass
def CSSViewlet(path, media="all", rel="stylesheet"):
@@ -76,7 +75,7 @@
klass = type('CSSViewlet',
(CSSResourceViewletBase, ViewletBase),
- {'index': ZopeTwoPageTemplateFile(src),
+ {'index': ViewPageTemplateFile(src),
'_path': path,
'_media':media,
'_rel':rel})
Modified: Zope/trunk/lib/python/Products/PageTemplates/PageTemplateFile.py
===================================================================
--- Zope/trunk/lib/python/Products/PageTemplates/PageTemplateFile.py 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/Products/PageTemplates/PageTemplateFile.py 2008-04-26 17:46:46 UTC (rev 85767)
@@ -17,7 +17,7 @@
import AccessControl
from Globals import package_home, InitializeClass, DevelopmentMode
from App.config import getConfiguration
-from Acquisition import aq_parent, aq_inner
+from Acquisition import aq_parent, aq_inner, aq_get
from ComputedAttribute import ComputedAttribute
from OFS.SimpleItem import SimpleItem
from OFS.Traversable import Traversable
@@ -87,7 +87,10 @@
self.filename = filename
def pt_getContext(self):
- root = self.getPhysicalRoot()
+ root = None
+ meth = aq_get(self, 'getPhysicalRoot', None)
+ if meth is not None:
+ root = meth()
context = self._getContext()
c = {'template': self,
'here': context,
@@ -96,7 +99,7 @@
'nothing': None,
'options': {},
'root': root,
- 'request': getattr(root, 'REQUEST', None),
+ 'request': aq_get(root, 'REQUEST', None),
'modules': SecureModuleImporter,
}
return c
@@ -108,12 +111,11 @@
kw['args'] = args
bound_names['options'] = kw
- try:
- response = self.REQUEST.RESPONSE
+ request = aq_get(self, 'REQUEST', None)
+ if request is not None:
+ response = request.response
if not response.headers.has_key('content-type'):
response.setHeader('content-type', self.content_type)
- except AttributeError:
- pass
# Execute the template in a new security context.
security = AccessControl.getSecurityManager()
Modified: Zope/trunk/lib/python/Products/PageTemplates/ZopePageTemplate.py
===================================================================
--- Zope/trunk/lib/python/Products/PageTemplates/ZopePageTemplate.py 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/Products/PageTemplates/ZopePageTemplate.py 2008-04-26 17:46:46 UTC (rev 85767)
@@ -17,6 +17,7 @@
import re
import os
import Acquisition
+from Acquisition import aq_get
from Globals import ImageFile, package_home, InitializeClass
from DateTime.DateTime import DateTime
from Shared.DC.Scripts.Script import Script
@@ -271,7 +272,10 @@
historyComparisonResults=html_diff(rev1._text, rev2._text) )
def pt_getContext(self, *args, **kw):
- root = self.getPhysicalRoot()
+ root = None
+ meth = aq_get(self, 'getPhysicalRoot', None)
+ if meth is not None:
+ root = meth()
context = self._getContext()
c = {'template': self,
'here': context,
@@ -280,7 +284,7 @@
'nothing': None,
'options': {},
'root': root,
- 'request': getattr(root, 'REQUEST', None),
+ 'request': aq_get(root, 'REQUEST', None),
'modules': SecureModuleImporter,
}
return c
@@ -302,12 +306,11 @@
kw['args'] = args
bound_names['options'] = kw
- try:
- response = self.REQUEST.RESPONSE
+ request = aq_get(self, 'REQUEST', None)
+ if request is not None:
+ response = request.response
if not response.headers.has_key('content-type'):
response.setHeader('content-type', self.content_type)
- except AttributeError:
- pass
security = getSecurityManager()
bound_names['user'] = security.getUser()
Modified: Zope/trunk/lib/python/Shared/DC/Scripts/Bindings.py
===================================================================
--- Zope/trunk/lib/python/Shared/DC/Scripts/Bindings.py 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/Shared/DC/Scripts/Bindings.py 2008-04-26 17:46:46 UTC (rev 85767)
@@ -20,6 +20,7 @@
from AccessControl.PermissionRole import _what_not_even_god_should_do
from AccessControl.ZopeGuards import guarded_getattr
from Persistence import Persistent
+from Acquisition import aq_parent, aq_inner
from string import join, strip
import re
@@ -179,6 +180,13 @@
# Make *extra* sure that the wrapper isn't used to access
# __call__, etc.
if name.startswith('__'):
+ # Acquisition will nowadays try to do an getattr on all
+ # objects which aren't Acquisition wrappers, asking for a
+ # __parent__ pointer. We don't want to raise Unauthorized
+ # in this case but simply an AttributeError.
+ if name in ('__parent__', '__name__'):
+ raise AttributeError(name)
+
self.__you_lose()
return guarded_getattr(self._wrapped, name, default)
@@ -264,11 +272,11 @@
def _getContext(self):
# Utility for bindcode.
while 1:
- self = self.aq_parent
+ self = aq_parent(self)
if not getattr(self, '_is_wrapperish', None):
- parent = getattr(self, 'aq_parent', None)
- inner = getattr(self, 'aq_inner', None)
- container = getattr(inner, 'aq_parent', None)
+ parent = aq_parent(self)
+ inner = aq_inner(self)
+ container = aq_parent(inner)
try: getSecurityManager().validate(parent, container, '', self)
except Unauthorized:
return UnauthorizedBinding('context', self)
@@ -277,11 +285,11 @@
def _getContainer(self):
# Utility for bindcode.
while 1:
- self = self.aq_inner.aq_parent
+ self = aq_parent(aq_inner(self))
if not getattr(self, '_is_wrapperish', None):
- parent = getattr(self, 'aq_parent', None)
- inner = getattr(self, 'aq_inner', None)
- container = getattr(inner, 'aq_parent', None)
+ parent = aq_parent(self)
+ inner = aq_inner(self)
+ container = aq_parent(inner)
try: getSecurityManager().validate(parent, container, '', self)
except Unauthorized:
return UnauthorizedBinding('container', self)
Modified: Zope/trunk/lib/python/ZPublisher/BaseRequest.py
===================================================================
--- Zope/trunk/lib/python/ZPublisher/BaseRequest.py 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/ZPublisher/BaseRequest.py 2008-04-26 17:46:46 UTC (rev 85767)
@@ -18,6 +18,7 @@
import xmlrpc
from zExceptions import Forbidden, Unauthorized, NotFound
from Acquisition import aq_base
+from Acquisition.interfaces import IAcquirer
from zope.interface import implements, providedBy, Interface
from zope.component import queryMultiAdapter
@@ -95,7 +96,7 @@
request.response.setStatus(200)
# We don't need to do the docstring security check
# for views, so lets skip it and return the object here.
- return subobject.__of__(object)
+ return subobject
# No view found. Reraise the error raised by __bobo_traverse__
raise e
else:
@@ -105,9 +106,10 @@
subobject = getattr(object, name)
else:
# We try to fall back to a view:
- subobject = queryMultiAdapter((object, request), Interface, name)
+ subobject = queryMultiAdapter((object, request), Interface,
+ name)
if subobject is not None:
- return subobject.__of__(object)
+ return subobject
# And lastly, of there is no view, try acquired attributes, but
# only if there is no __bobo_traverse__:
@@ -312,7 +314,9 @@
except TraversalError:
raise KeyError(ob, name)
- return ob2.__of__(ob)
+ if IAcquirer.providedBy(ob2):
+ ob2 = ob2.__of__(ob)
+ return ob2
if name == '.':
return ob
@@ -427,7 +431,7 @@
else:
# If we have reached the end of the path, we look to see
# if we can find IBrowserPublisher.browserDefault. If so,
- # we call it to let the object tell us how to publish it
+ # we call it to let the object tell us how to publish it.
# BrowserDefault returns the object to be published
# (usually self) and a sequence of names to traverse to
# find the method to be published.
@@ -440,7 +444,8 @@
not hasattr(object,'__bobo_traverse__')):
if object.aq_parent is not object.aq_inner.aq_parent:
from webdav.NullResource import NullResource
- object = NullResource(parents[-2], object.getId(), self).__of__(parents[-2])
+ object = NullResource(parents[-2], object.getId(),
+ self).__of__(parents[-2])
if IBrowserPublisher.providedBy(object):
adapter = object
@@ -451,10 +456,9 @@
# Zope2 doesn't set up its own adapters in a lot
# of cases so we will just use a default adapter.
adapter = DefaultPublishTraverse(object, self)
-
- newobject, default_path = adapter.browserDefault(self)
- if default_path or newobject is not object:
- object = newobject
+
+ object, default_path = adapter.browserDefault(self)
+ if default_path:
request._hacked_path=1
if len(default_path) > 1:
path = list(default_path)
Modified: Zope/trunk/lib/python/ZPublisher/HTTPRequest.py
===================================================================
--- Zope/trunk/lib/python/ZPublisher/HTTPRequest.py 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/ZPublisher/HTTPRequest.py 2008-04-26 17:46:46 UTC (rev 85767)
@@ -1116,7 +1116,7 @@
clone['PARENTS']=[self['PARENTS'][-1]]
return clone
- def get_header(self, name, default=None):
+ def getHeader(self, name, default=None, literal=False):
"""Return the named HTTP header, or an optional default
argument or None if the header is not found. Note that
both original and CGI-ified header names are recognized,
@@ -1124,7 +1124,8 @@
should all return the Content-Type header, if available.
"""
environ=self.environ
- name=('_'.join(name.split("-"))).upper()
+ if not literal:
+ name = name.replace('-', '_').upper()
val=environ.get(name, None)
if val is not None:
return val
@@ -1132,6 +1133,8 @@
name='HTTP_%s' % name
return environ.get(name, default)
+ get_header = getHeader # BBB
+
def get(self, key, default=None, returnTaints=0,
URLmatch=re.compile('URL(PATH)?([0-9]+)$').match,
BASEmatch=re.compile('BASE(PATH)?([0-9]+)$').match,
Modified: Zope/trunk/lib/python/ZPublisher/mapply.py
===================================================================
--- Zope/trunk/lib/python/ZPublisher/mapply.py 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/ZPublisher/mapply.py 2008-04-26 17:46:46 UTC (rev 85767)
@@ -12,6 +12,7 @@
##############################################################################
"""Provide an apply-like facility that works with any mapping object
"""
+import zope.publisher.publish
def default_call_object(object, args, context):
result=object(*args) # Type s<cr> to step into published object.
@@ -39,28 +40,16 @@
if hasattr(object,'__bases__'):
f, names, defaults = handle_class(object, context)
else:
- f=object
- im=0
- if hasattr(f, 'im_func'):
- im=1
- elif not hasattr(f,'func_defaults'):
- if hasattr(f, '__call__'):
- f=f.__call__
- if hasattr(f, 'im_func'):
- im=1
- elif not hasattr(f,'func_defaults') and maybe: return object
- elif maybe: return object
+ try:
+ f, count = zope.publisher.publish.unwrapMethod(object)
+ except TypeError:
+ if maybe:
+ return object
+ raise
+ code = f.func_code
+ defaults = f.func_defaults
+ names = code.co_varnames[count:code.co_argcount]
- if im:
- f=f.im_func
- c=f.func_code
- defaults=f.func_defaults
- names=c.co_varnames[1:c.co_argcount]
- else:
- defaults=f.func_defaults
- c=f.func_code
- names=c.co_varnames[:c.co_argcount]
-
nargs=len(names)
if positional:
positional=list(positional)
Modified: Zope/trunk/lib/python/ZPublisher/tests/testBaseRequest.py
===================================================================
--- Zope/trunk/lib/python/ZPublisher/tests/testBaseRequest.py 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/ZPublisher/tests/testBaseRequest.py 2008-04-26 17:46:46 UTC (rev 85767)
@@ -6,6 +6,7 @@
import zope.component
import zope.testing.cleanup
import zope.traversing.namespace
+from zope.publisher.browser import BrowserPage
from zope.publisher.browser import IBrowserRequest
from zope.publisher.browser import IDefaultBrowserLayer
from zope.traversing.interfaces import ITraversable
@@ -276,9 +277,15 @@
gsm = zope.component.getGlobalSiteManager()
- # Define our 'meth' view
- gsm.registerAdapter(DummyView, (IDummy, IDefaultBrowserLayer), None,
- 'meth')
+ # Define the views
+ gsm.registerAdapter(DummyView, (IDummy, IDefaultBrowserLayer),
+ zope.interface.Interface, 'meth')
+ gsm.registerAdapter(DummyPage, (IDummy, IDefaultBrowserLayer),
+ zope.interface.Interface, 'page')
+ gsm.registerAdapter(DummyPage2, (IDummy, IDefaultBrowserLayer),
+ zope.interface.Interface, 'page2')
+ gsm.registerAdapter(DummyPage3, (IDummy, IDefaultBrowserLayer),
+ zope.interface.Interface, 'page3')
# Bind the 'view' namespace (for @@ traversal)
gsm.registerAdapter(zope.traversing.namespace.view,
@@ -382,7 +389,28 @@
r.traverse('folder/obj/++view++meth')
self.assertEqual(r['URL'], '/folder/obj/++view++meth')
+ def test_browserDefault(self):
+ # browserDefault can return self, () to indicate that the
+ # object itself wants to be published (using __call__):
+ root, folder = self._makeRootAndFolder()
+ folder._setObject('obj', DummyObjectZ3('obj'))
+ r = self._makeOne(root)
+ ob = r.traverse('folder/obj/page')
+ self.assertEqual(ob(), 'Test page')
+ # browserDefault can return another_object, () to indicate
+ # that that object should be published (using __call__):
+ r = self._makeOne(root)
+ ob = r.traverse('folder/obj/page2')
+ self.assertEqual(ob(), 'Test page')
+
+ # browserDefault can also return self.some_method, () to
+ # indicate that that method should be called.
+ r = self._makeOne(root)
+ ob = r.traverse('folder/obj/page3')
+ self.assertEqual(ob(), 'Test page')
+
+
class DummyResponse(Implicit):
base = ''
@@ -512,6 +540,34 @@
def __call__(self):
return 'view on %s' % (self.content.name)
+class DummyPage(BrowserPage):
+
+ # BrowserPage is an IBrowserPublisher with a browserDefault that
+ # returns self, () so that __call__ is invoked by the publisher.
+
+ def __call__(self):
+ return 'Test page'
+
+class DummyPage2(BrowserPage):
+
+ def browserDefault(self, request):
+ # intentionally return something that's not self
+ return DummyPage(self.context, request), ()
+
+ # __call__ remains unimplemented, baseclass raises NotImplementedError
+
+class DummyPage3(BrowserPage):
+
+ def browserDefault(self, request):
+ # intentionally return a method here
+ return self.foo, ()
+
+ def foo(self):
+ return 'Test page'
+
+ # __call__ remains unimplemented, baseclass raises NotImplementedError
+
+
def test_suite():
return unittest.TestSuite((
unittest.makeSuite(TestBaseRequest),
@@ -519,4 +575,4 @@
))
if __name__ == '__main__':
- unitttest.main(defaultTest='test_suite')
+ unittest.main(defaultTest='test_suite')
Modified: Zope/trunk/lib/python/ZPublisher/tests/testHTTPRequest.py
===================================================================
--- Zope/trunk/lib/python/ZPublisher/tests/testHTTPRequest.py 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/ZPublisher/tests/testHTTPRequest.py 2008-04-26 17:46:46 UTC (rev 85767)
@@ -1,6 +1,15 @@
+import sys
+import base64
import unittest
from urllib import quote_plus
-
+from types import ListType, TupleType, StringType, UnicodeType
+from StringIO import StringIO
+
+from DateTime import DateTime
+from ZPublisher.HTTPRequest import HTTPRequest, record, trusted_proxies
+from ZPublisher.TaintedString import TaintedString
+from ZPublisher.Converters import type_converters
+
TEST_LARGEFILE_DATA = '''
--12345
Content-Disposition: form-data; name="file"; filename="file"
@@ -13,13 +22,10 @@
class AuthCredentialsTests( unittest.TestCase ):
def _getTargetClass(self):
- from ZPublisher.HTTPRequest import HTTPRequest
return HTTPRequest
def _makeOne(self, stdin=None, environ=None, response=None, clean=1):
-
if stdin is None:
- from StringIO import StringIO
stdin = StringIO()
if environ is None:
@@ -40,9 +46,6 @@
return self._getTargetClass()(stdin, environ, response, clean)
def test__authUserPW_simple( self ):
-
- import base64
-
user_id = 'user'
password = 'password'
encoded = base64.encodestring( '%s:%s' % ( user_id, password ) )
@@ -57,11 +60,7 @@
self.assertEqual( password_x, password )
def test__authUserPW_with_embedded_colon( self ):
-
# http://www.zope.org/Collectors/Zope/2039
-
- import base64
-
user_id = 'user'
password = 'embedded:colon'
encoded = base64.encodestring( '%s:%s' % ( user_id, password ) )
@@ -75,43 +74,20 @@
self.assertEqual( user_id_x, user_id )
self.assertEqual( password_x, password )
-class RecordTests( unittest.TestCase ):
- def test_repr( self ):
- from ZPublisher.HTTPRequest import record
- record = record()
- record.a = 1
- record.b = 'foo'
- r = repr( record )
- d = eval( r )
- self.assertEqual( d, record.__dict__ )
+class RecordTests(unittest.TestCase):
- def test_contains(self):
- from ZPublisher.HTTPRequest import record
- record = record()
- record.a = 1
- self.assertTrue('a' in record)
+ def test_repr(self):
+ rec = record()
+ rec.a = 1
+ rec.b = 'foo'
+ r = repr(rec)
+ d = eval(r)
+ self.assertEqual(d, rec.__dict__)
- def test_iter(self):
- from ZPublisher.HTTPRequest import record
- record = record()
- record.a = 1
- record.b = 2
- record.c = 3
- for k in record:
- self.assertTrue(k in ('a','b','c'))
- def test_len(self):
- from ZPublisher.HTTPRequest import record
- record = record()
- record.a = 1
- record.b = 2
- record.c = 3
- self.assertEqual(len(record), 3)
-
class ProcessInputsTests(unittest.TestCase):
def _getHTTPRequest(self, env):
- from ZPublisher.HTTPRequest import HTTPRequest
return HTTPRequest(None, env, None)
def _processInputs(self, inputs):
@@ -141,9 +117,6 @@
# when one is found.
# Also raises an Assertion if a string which *should* have been
# tainted is found, or when a tainted string is not deemed dangerous.
- from types import ListType, TupleType, StringType, UnicodeType
- from ZPublisher.HTTPRequest import record
- from ZPublisher.TaintedString import TaintedString
retval = 0
@@ -221,8 +194,6 @@
self._onlyTaintedformHoldsTaintedStrings(req)
def testSimpleMarshalling(self):
- from DateTime import DateTime
-
inputs = (
('num:int', '42'), ('fract:float', '4.2'), ('bign:long', '45'),
('words:string', 'Some words'), ('2tokens:tokens', 'one two'),
@@ -476,9 +447,6 @@
self._onlyTaintedformHoldsTaintedStrings(req)
def testSimpleContainersWithTaints(self):
- from types import ListType, TupleType
- from ZPublisher.HTTPRequest import record
-
inputs = (
('toneitem:list', '<one>'),
('<tkeyoneitem>:list', 'one'),
@@ -633,8 +601,6 @@
def testNoTaintedExceptions(self):
# Feed tainted garbage to the conversion methods, and any exception
# returned should be HTML safe
- from ZPublisher.Converters import type_converters
- from DateTime import DateTime
for type, convert in type_converters.items():
try:
convert('<html garbage>')
@@ -717,12 +683,10 @@
def testRemoveStdinReferences(self):
# Verifies that all references to the input stream go away on
# request.close(). Otherwise a tempfile may stick around.
- import sys
- from StringIO import StringIO
s = StringIO(TEST_FILE_DATA)
env = TEST_ENVIRON.copy()
start_count = sys.getrefcount(s)
- from ZPublisher.HTTPRequest import HTTPRequest
+
req = HTTPRequest(s, env, None)
req.processInputs()
self.assertNotEqual(start_count, sys.getrefcount(s)) # Precondition
@@ -731,10 +695,9 @@
def testFileName(self):
# checks fileupload object supports the filename
- from StringIO import StringIO
s = StringIO(TEST_LARGEFILE_DATA)
env = TEST_ENVIRON.copy()
- from ZPublisher.HTTPRequest import HTTPRequest
+
req = HTTPRequest(s, env, None)
req.processInputs()
f = req.form.get('file')
@@ -743,11 +706,9 @@
def testFileIterator(self):
# checks fileupload object supports the iterator protocol
# collector entry 1837
- import sys
- from StringIO import StringIO
s = StringIO(TEST_FILE_DATA)
env = TEST_ENVIRON.copy()
- from ZPublisher.HTTPRequest import HTTPRequest
+
req = HTTPRequest(s, env, None)
req.processInputs()
f=req.form.get('file')
@@ -763,8 +724,6 @@
'SERVER_NAME': 'localhost',
'SERVER_PORT': '80',
}
- from StringIO import StringIO
- from ZPublisher.HTTPRequest import HTTPRequest
from zope.publisher.base import DebugFlags
s = StringIO('')
@@ -881,8 +840,6 @@
'SERVER_NAME': 'localhost',
'SERVER_PORT': '80',
}
- from StringIO import StringIO
- from ZPublisher.HTTPRequest import HTTPRequest
s = StringIO('')
env = TEST_ENVIRON.copy()
@@ -902,8 +859,6 @@
'REMOTE_ADDR': '127.0.0.1',
'HTTP_X_FORWARDED_FOR': '10.1.20.30, 192.168.1.100',
}
- from StringIO import StringIO
- from ZPublisher.HTTPRequest import HTTPRequest, trusted_proxies
s = StringIO('')
env = TEST_ENVIRON.copy()
@@ -925,7 +880,30 @@
request = HTTPRequest(s, env, None)
self.assertEqual(request.getClientAddr(), '')
+ def testGetHeader(self):
+ s = StringIO('')
+ env = TEST_ENVIRON.copy()
+ request = HTTPRequest(s, env, None)
+ self.assertEqual(request.getHeader('Content-Type'),
+ 'multipart/form-data; boundary=12345')
+
+ # getHeader is agnostic of case
+ self.assertEqual(request.getHeader('content-type'),
+ 'multipart/form-data; boundary=12345')
+
+ # and of dashes vs. underscores
+ self.assertEqual(request.getHeader('content_type'),
+ 'multipart/form-data; boundary=12345')
+
+ # the 'literal' argument can turn this normalization off:
+ self.assertEqual(request.getHeader('Content-Type', literal=True), None)
+
+ # the 'default' argument can be used to get something other than
+ # None when the lookup fails:
+ self.assertEqual(request.getHeader('Not-existant', default='Whatever'),
+ 'Whatever')
+
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(AuthCredentialsTests, 'test'))
Copied: Zope/trunk/lib/python/ZPublisher/tests/test_mapply.py (from rev 85463, Zope/branches/philikon-aq/lib/python/ZPublisher/tests/test_mapply.py)
===================================================================
--- Zope/trunk/lib/python/ZPublisher/tests/test_mapply.py (rev 0)
+++ Zope/trunk/lib/python/ZPublisher/tests/test_mapply.py 2008-04-26 17:46:46 UTC (rev 85767)
@@ -0,0 +1,98 @@
+##############################################################################
+#
+# Copyright (c) 2007 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Test mapply() function
+"""
+import unittest
+import ExtensionClass
+import Acquisition
+from ZPublisher.mapply import mapply
+
+class MapplyTests(unittest.TestCase):
+
+ def testMethod(self):
+ def compute(a,b,c=4):
+ return '%d%d%d' % (a, b, c)
+ values = {'a':2, 'b':3, 'c':5}
+ v = mapply(compute, (), values)
+ self.failUnlessEqual(v, '235')
+
+ v = mapply(compute, (7,), values)
+ self.failUnlessEqual(v, '735')
+
+ def testClass(self):
+ values = {'a':2, 'b':3, 'c':5}
+ class c(object):
+ a = 3
+ def __call__(self, b, c=4):
+ return '%d%d%d' % (self.a, b, c)
+ compute = __call__
+ cc = c()
+ v = mapply(cc, (), values)
+ self.failUnlessEqual(v, '335')
+
+ del values['c']
+ v = mapply(cc.compute, (), values)
+ self.failUnlessEqual(v, '334')
+
+ class c2:
+ """Must be a classic class."""
+
+ c2inst = c2()
+ c2inst.__call__ = cc
+ v = mapply(c2inst, (), values)
+ self.failUnlessEqual(v, '334')
+
+ def testObjectWithCall(self):
+ # Make sure that the __call__ of an object can also be another
+ # callable object. mapply will do the right thing and
+ # recursive look for __call__ attributes until it finds an
+ # actual method:
+
+ class CallableObject:
+ def __call__(self, a, b):
+ return '%s%s' % (a, b)
+
+ class Container:
+ __call__ = CallableObject()
+
+ v = mapply(Container(), (8, 3), {})
+ self.assertEqual(v, '83')
+
+ def testUncallableObject(self):
+ # Normally, mapply will raise a TypeError if it encounters an
+ # uncallable object (e.g. an interger ;))
+ self.assertRaises(TypeError, mapply, 2, (), {})
+
+ # Unless you enable the 'maybe' flag, in which case it will
+ # only maybe call the object
+ self.assertEqual(mapply(2, (), {}, maybe=True), 2)
+
+ def testNoCallButAcquisition(self):
+ # Make sure that mapply won't erroneously walk up the
+ # Acquisition chain when looking for __call__ attributes:
+
+ class Root(ExtensionClass.Base):
+ def __call__(self):
+ return 'The root __call__'
+
+ class NoCallButAcquisition(Acquisition.Implicit):
+ pass
+
+ ob = NoCallButAcquisition().__of__(Root())
+ self.assertRaises(TypeError, mapply, ob, (), {})
+
+def test_suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(MapplyTests))
+ return suite
Modified: Zope/trunk/lib/python/Zope2/App/startup.py
===================================================================
--- Zope/trunk/lib/python/Zope2/App/startup.py 2008-04-26 17:36:12 UTC (rev 85766)
+++ Zope/trunk/lib/python/Zope2/App/startup.py 2008-04-26 17:46:46 UTC (rev 85767)
@@ -18,6 +18,9 @@
from AccessControl.SecurityManagement import newSecurityManager
from AccessControl.SecurityManagement import noSecurityManager
from Acquisition import aq_acquire
+from Acquisition import aq_base
+from Acquisition import aq_inner
+from Acquisition import aq_parent
from App.config import getConfiguration
from time import asctime
from types import StringType, ListType
@@ -130,7 +133,7 @@
newSecurityManager(request, user)
version = request.get(Globals.VersionNameName, '')
if version:
- object = user.aq_parent
+ object = aq_parent(user)
if not getSecurityManager().checkPermission(
'Join/leave Versions', object):
request['RESPONSE'].setCookie(
@@ -231,7 +234,7 @@
while 1:
f = getattr(published, self.raise_error_message, None)
if f is None:
- published = getattr(published, 'aq_parent', None)
+ published = aq_parent(published)
if published is None:
raise t, v, traceback
else:
@@ -241,8 +244,10 @@
while 1:
if getattr(client, self.error_message, None) is not None:
break
- client = getattr(client, 'aq_parent', None)
- if client is None:
+ client = aq_parent(client)
+ # If we are going in circles without getting the error_message
+ # just raise
+ if client is None or aq_base(client) is aq_base(published):
raise t, v, traceback
if REQUEST.get('AUTHENTICATED_USER', None) is None:
@@ -296,8 +301,7 @@
object = None
break
to_append = (object.__name__,) + to_append
- object = getattr(object, 'aq_inner', object)
- object = getattr(object, 'aq_parent', None)
+ object = aq_parent(aq_inner(object))
if object is not None:
path = '/'.join(object.getPhysicalPath() + to_append)
@@ -312,11 +316,8 @@
T.note(path)
auth_user=request_get('AUTHENTICATED_USER',None)
if auth_user is not None:
- try:
- auth_folder = auth_user.aq_parent
- except AttributeError:
- # Most likely some product forgot to call __of__()
- # on the user object.
+ auth_folder = aq_parent(auth_user)
+ if auth_folder is None:
ac_logger.warning(
'A user object of type %s has no aq_parent.',
type(auth_user)
@@ -326,6 +327,3 @@
auth_path = '/'.join(auth_folder.getPhysicalPath()[1:-1])
T.setUser(auth_user.getId(), auth_path)
-
-
-
More information about the Zope-Checkins
mailing list