[Checkins] SVN: z3c.dav/trunk/src/z3c/dav/ Account for
zope.app.http.interfaces.INullResource objects when processing the
Michael Kerrin
michael.kerrin at openapp.ie
Tue Jun 12 14:41:13 EDT 2007
Log message for revision 76645:
Account for zope.app.http.interfaces.INullResource objects when processing the
IF header.
Changed:
U z3c.dav/trunk/src/z3c/dav/configure.zcml
U z3c.dav/trunk/src/z3c/dav/ifvalidator.py
-=-
Modified: z3c.dav/trunk/src/z3c/dav/configure.zcml
===================================================================
--- z3c.dav/trunk/src/z3c/dav/configure.zcml 2007-06-12 18:17:18 UTC (rev 76644)
+++ z3c.dav/trunk/src/z3c/dav/configure.zcml 2007-06-12 18:41:12 UTC (rev 76645)
@@ -74,9 +74,10 @@
/>
<adapter
- for="zope.interface.Interface
- zope.publisher.interfaces.http.IHTTPRequest
- zope.interface.Interface"
+ factory=".ifvalidator.StateTokensForNullResource"
+ />
+
+ <adapter
factory=".ifvalidator.StateTokens"
/>
Modified: z3c.dav/trunk/src/z3c/dav/ifvalidator.py
===================================================================
--- z3c.dav/trunk/src/z3c/dav/ifvalidator.py 2007-06-12 18:17:18 UTC (rev 76644)
+++ z3c.dav/trunk/src/z3c/dav/ifvalidator.py 2007-06-12 18:41:12 UTC (rev 76645)
@@ -181,11 +181,21 @@
... def getPath(self):
... return '/' + self.context.__name__
>>> zope.component.getGlobalSiteManager().registerAdapter(
- ... PhysicallyLocatable, (zope.interface.Interface,))
+ ... PhysicallyLocatable, (Demo,))
+ We store the results of the parsing of the state tokens in a dictionary
+ structure. Its keys are the path to the object and its value is a
+ dictionary of locktokens and whether or not the `NOT` keyword was present.
+
+ >>> def getStateResults(request):
+ ... reqannot = zope.annotation.interfaces.IAnnotations(request)
+ ... return reqannot.get(STATE_ANNOTS, {})
+
Firstly nothing matches the <DAV:no-lock> state token.
>>> resource = Demo('test')
+ >>> validator.valid(resource, request, None)
+ False
>>> resource._tokens = ['test']
>>> validator.valid(resource, request, None)
False
@@ -258,7 +268,7 @@
>>> validator.valid(resource, request, None)
True
>>> getStateResults(request)
- {'/test': {'locktoken': True}}
+ {'/test': {'locktoken': False}}
If there are multiple locktokens associated with a resource, we only
are interested in the token that is represented in the IF header, so we
@@ -268,10 +278,12 @@
>>> validator.valid(resource, request, None)
True
>>> getStateResults(request)
- {'/test': {'locktoken': True}}
+ {'/test': {'locktoken': False}}
>>> request._environ['IF'] = '(NOT <locktoken>)'
>>> validator.valid(resource, request, None)
False
+ >>> getStateResults(request)
+ {'/test': {'locktoken': True}}
>>> request._environ['IF'] = '(NOT <invalidlocktoken>)'
>>> validator.valid(resource, request, None)
@@ -297,15 +309,15 @@
>>> validator.valid(resource, request, None)
True
- Now if the resource isn't locked and has no state tokens associated with
- it, then the request must be valid.
+ Now if the resource isn't locked, that is it has no state tokens
+ associated with it, then the request must be valid.
>>> resource._tokens = []
>>> request._environ['IF'] = '(<locktoken>)'
>>> validator.valid(resource, request, None)
True
>>> getStateResults(request)
- {'/test': {'locktoken': True}}
+ {'/test': {'locktoken': False}}
But we if the condition in the state token contains a state token belong
to the URL scheme that we don't know about then the condition false, and
@@ -381,7 +393,7 @@
>>> validator.valid(demo, request, None)
False
>>> getStateResults(request)
- {}
+ {'/missing': {'locked': True}}
If we have specify multiple resources then we need to parse the
whole `IF` header so that the state results method knows about the
@@ -391,8 +403,115 @@
>>> validator.valid(demo, request, None)
True
>>> getStateResults(request)
- {'/locked': {'notlocked': False}, '/demo': {'demo': True}}
+ {'/locked': {'notlocked': False}, '/demo': {'demo': False}}
+ Null resources
+ ==============
+
+ >>> import zope.app.http.put
+
+ When validating certain tagged-list productions we can get back
+ zope.app.http.interfaces.INullResource objects from the get_resource
+ method, plus the original context can be a null resource too.
+
+ >>> class Demo2(Demo):
+ ... def publishTraverse(self, request, name):
+ ... child = self.children.get(name, None)
+ ... if child:
+ ... return child
+ ... if request.method in ('PUT', 'LOCK', 'MKCOL'):
+ ... return zope.app.http.put.NullResource(self, name)
+ ... raise zope.publisher.interfaces.NotFound(self, name, request)
+
+ >>> root2 = Demo2('')
+ >>> locked2 = Demo2('locked')
+ >>> root2.add(locked2)
+ >>> demo2 = Demo2('demo')
+ >>> root2.add(demo2)
+
+ >>> class PhysicallyLocatable2(PhysicallyLocatable):
+ ... def getRoot(self):
+ ... return root2
+ >>> zope.component.getGlobalSiteManager().registerAdapter(
+ ... PhysicallyLocatable2, (Demo2,))
+
+ Now generate a request with an IF header, and LOCK method that fails to
+ find a resource, we get a NullResource instead of a NotFound exception.
+
+ >>> request = TestRequest(environ = {'IF': '</missing> (<locked>)',
+ ... 'REQUEST_METHOD': 'LOCK'})
+ >>> request.setPublication(ZopePublication(None))
+
+ >>> missingdemo2 = validator.get_resource(demo2, request, '/missing')
+ >>> missingdemo2 = removeSecurityProxy(missingdemo2)
+ >>> missingdemo2 #doctest:+ELLIPSIS
+ <zope.app.http.put.NullResource object at ...>
+
+ Now this request evaluates to true and we have no state tokens since, we
+ have no path to compare the results against.
+
+ >>> validator.valid(demo2, request, None)
+ True
+ >>> getStateResults(request)
+ {'/missing': {'locked': False}}
+ >>> matchesIfHeader(demo2, request)
+ True
+ >>> matchesIfHeader(missingdemo2, request)
+ True
+
+ But if the root2 folder is locked then we consider our selves to be in
+ the scope of the lock.
+
+ >>> root2._tokens = ['locked']
+ >>> validator.valid(demo2, request, None)
+ True
+ >>> getStateResults(request)
+ {'/missing': {'locked': False}}
+ >>> matchesIfHeader(demo2, request)
+ False
+ >>> validator.valid(missingdemo2, request, None)
+ True
+ >>> getStateResults(request)
+ {'/missing': {'locked': False}}
+ >>> matchesIfHeader(missingdemo2, request)
+ True
+
+ Even though the correct lock token is supplied in the `IF` header for the
+ root2 resource, it is on for an alternative resource and the root object
+ is not within the scope of that indirect lock token.
+
+ >>> matchesIfHeader(root2, request)
+ False
+
+ >>> root2._tokens = ['otherlocktoken']
+ >>> validator.valid(demo2, request, None)
+ True
+ >>> getStateResults(request)
+ {'/missing': {'locked': False}}
+
+ >>> validator.valid(missingdemo2, request, None)
+ True
+ >>> matchesIfHeader(missingdemo2, request)
+ True
+
+ >>> root2._tokens = ['locked']
+ >>> validator.valid(missingdemo2, request, None)
+ True
+
+ When a null resource is created it if sometimes replaced by an other
+ persistent object. If this is a PUT method then the object created
+ should be locked with an indirect lock token, associated with the same
+ root token as the folder, so as the matchesIfHeader method returns
+ True.
+
+ >>> demo3 = Demo('missing')
+ >>> root2.add(demo3)
+ >>> matchesIfHeader(demo3, request)
+ False
+ >>> demo3._tokens = ['locked']
+ >>> matchesIfHeader(demo3, request)
+ True
+
Invalid data
============
@@ -563,8 +682,11 @@
... Statetokens, (None, TestRequest, None))
True
>>> zope.component.getGlobalSiteManager().unregisterAdapter(
- ... PhysicallyLocatable, (zope.interface.Interface,))
+ ... PhysicallyLocatable, (Demo,))
True
+ >>> zope.component.getGlobalSiteManager().unregisterAdapter(
+ ... PhysicallyLocatable2, (Demo2,))
+ True
"""
zope.interface.implements(z3c.conditionalviews.interfaces.IHTTPValidator)
@@ -649,10 +771,11 @@
# against the current context.
context = None
- if context is not None:
+ if context is None or \
+ zope.app.http.interfaces.INullResource.providedBy(context):
+ path = resource and urlparse.urlparse(resource)[2]
+ else:
path = zope.traversing.api.getPath(context)
- else:
- path = None
etag = zope.component.queryMultiAdapter(
(context, request, view),
@@ -680,21 +803,23 @@
# if we understand the scheme of the state token
# supplied in this condition. If we don't understand
# the scheme then this condition evaluates to False.
- if states and \
- condition.state_token.scheme in states.schemes:
- # Now if the context as at least one state token
- # then we compare the state token supplied in this
- # condition by simple string comparison.
- if statetokens and \
- condition.state_token.token not in \
- statetokens:
+ if states:
+ if condition.state_token.scheme in states.schemes:
+ # Now if the context as at least one state token
+ # then we compare the state token supplied in this
+ # condition by simple string comparison.
+ if statetokens and \
+ condition.state_token.token not in \
+ statetokens:
+ result = False
+ else:
+ result = True
+ else:
+ # Un-known state token scheme so this condition
+ # is False.
result = False
- else:
- result = True
else:
- # Un-known state token scheme so this condition
- # is False.
- result = False
+ result = True
if condition.notted:
result = not result
@@ -704,7 +829,7 @@
# have a path.
stateresults.setdefault(path, {})
stateresults[path][
- condition.state_token.token] = result
+ condition.state_token.token] = condition.notted
else:
raise TypeError(
"Either the entity_tag or the state_token"
@@ -733,11 +858,6 @@
pass # do nothing
-def getStateResults(request):
- reqannot = zope.annotation.interfaces.IAnnotations(request)
- return reqannot.get(STATE_ANNOTS, {})
-
-
def matchesIfHeader(context, request):
# Test the state of the context to see if matches the list of states
# supplied in the `IF` header.
@@ -757,7 +877,7 @@
parsedstates = stateresults.get(
zope.traversing.api.getPath(context), {})
for locktoken in states:
- if parsedstates.get(locktoken, False):
+ if locktoken in parsedstates:
return True
if parsedstates:
# None of the statetokens in the passed.
@@ -821,6 +941,9 @@
"""
zope.interface.implements(IStateTokens)
+ zope.component.adapts(zope.interface.Interface,
+ zope.publisher.interfaces.http.IHTTPRequest,
+ zope.interface.Interface)
def __init__(self, context, request, view):
self.context = context
@@ -839,3 +962,17 @@
for activelock in lockdiscovery:
locktokens.extend(activelock.locktoken)
return locktokens
+
+
+class StateTokensForNullResource(object):
+ zope.interface.implements(IStateTokens)
+ zope.component.adapts(zope.app.http.interfaces.INullResource,
+ zope.publisher.interfaces.http.IHTTPRequest,
+ zope.interface.Interface)
+
+ def __init__(self, context, request, view):
+ pass
+
+ schemes = ("opaquelocktoken",)
+
+ tokens = []
More information about the Checkins
mailing list