1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
|
import urllib
import urlparse
from zope.interface import classProvides
from zope.interface import implements
from repoze.bfg.location import locate
from repoze.bfg.location import lineage
from repoze.bfg.interfaces import ILocation
from repoze.bfg.interfaces import ITraverser
from repoze.bfg.interfaces import ITraverserFactory
def split_path(path):
if path.startswith('/'):
path = path[1:]
if path.endswith('/'):
path = path[:-1]
clean=[]
for segment in path.split('/'):
segment = urllib.unquote(segment) # deal with spaces in path segment
if not segment or segment=='.':
continue
elif segment == '..':
del clean[-1]
else:
clean.append(segment)
return clean
def step(ob, name, default):
if name.startswith('@@'):
return name[2:], default
if not hasattr(ob, '__getitem__'):
return name, default
try:
return name, ob[name]
except KeyError:
return name, default
_marker = ()
class ModelGraphTraverser(object):
classProvides(ITraverserFactory)
implements(ITraverser)
def __init__(self, root):
self.root = root
self.locatable = ILocation.providedBy(root)
def __call__(self, environ):
path = environ.get('PATH_INFO', '/')
path = split_path(path)
root = self.root
ob = self.root
name = ''
while path:
segment = path.pop(0)
segment, next = step(ob, segment, _marker)
if next is _marker:
name = segment
break
if self.locatable:
next = locate(next, ob, segment)
ob = next
return ob, name, path
def find_root(model):
""" Find the root node in the graph to which ``model``
belongs. Note that ``model`` should be :term:`location`-aware."""
for location in lineage(model):
if location.__parent__ is None:
model = location
break
return model
def find_model(model, path):
""" Given a model object and a string representing a path
reference (a set of names delimited by forward-slashes), return an
context in this application's model graph at the specified path.
If the path starts with a slash, the path is considered absolute
and the graph traversal will start at the root object. If the
path does not start with a slash, the path is considered relative
and graph traversal will begin at the model object supplied to the
function. In either case, if the path cannot be resolved, a
KeyError will be thrown. Note that the model passed in should be
:term:`location`-aware."""
if path.startswith('/'):
model = find_root(model)
ob, name, path = ITraverserFactory(model)({'PATH_INFO':path})
if name:
raise KeyError('%r has no subelement %s' % (ob, name))
return ob
def find_interface(model, interface):
""" Return the first object found which provides the interface
``interface`` in the parent chain of ``model`` or ``None`` if no
object providing ``interface`` can be found in the parent chain.
The ``model`` passed in should be :term:`location`-aware."""
for location in lineage(model):
if interface.providedBy(location):
return location
def model_url(model, request, *elements):
""" Return the absolute URL of the model object based on the
``wsgi.url_scheme``, ``HTTP_HOST`` or ``SERVER_NAME`` in the
request, plus any ``SCRIPT_NAME``. Any positional arguments
passed in as ``elements`` will be joined by slashes and appended
to the generated URL. The passed in elements are *not*
URL-quoted. The ``model`` passed in must be
:term:`location`-aware."""
rpath = []
for location in lineage(model):
if location.__name__:
rpath.append(urllib.quote(location.__name__))
path = list(reversed(rpath))
path.extend(elements)
path = '/'.join(path)
return urlparse.urljoin(request.application_url, path)
def model_path(model, *elements):
""" Return a string representing the absolute path of the model
object based on its position in the model graph, e.g
``/foo/bar``. Any positional arguments passed in as ``elements``
will be joined by slashes and appended to the generated path. The
``model`` passed in must be :term:`location`-aware."""
rpath = []
for location in lineage(model):
if location.__name__:
rpath.append(location.__name__)
path = list(reversed(rpath))
path.extend(elements)
path.insert(0, '')
return '/'.join(path)
|