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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
|
= Serving directories of files =
LAZR adds special views that can be used to serve all the files under a
particular directory.
== ExportedFolder ==
This is the base implementation. To export a directory, you need to
subclass that view and provide a folder property returning the path of
the directory to expose.
>>> import os
>>> import tempfile
>>> resource_dir = tempfile.mkdtemp(prefix='resources')
>>> file(os.path.join(resource_dir, 'test.txt'), 'w').write('Text file')
>>> file(os.path.join(resource_dir, 'image1.gif'), 'w').write(
... 'GIF file')
>>> file(os.path.join(resource_dir, 'image2.png'), 'w').write(
... 'PNG file')
>>> os.mkdir(os.path.join(resource_dir, 'a_dir'))
>>> file(os.path.join(resource_dir, 'other.txt'), 'w').write(
... 'Other file')
>>> from lp.app.browser.folder import ExportedFolder
>>> class MyFolder(ExportedFolder):
... folder = resource_dir
That view provides the IBrowserPublisher interface necessary to handle
all the traversal logic.
>>> from zope.interface.verify import verifyObject
>>> from zope.publisher.interfaces.browser import IBrowserPublisher
>>> from lazr.restful.testing.webservice import FakeRequest
>>> view = MyFolder(object(), FakeRequest(version="devel"))
>>> verifyObject(IBrowserPublisher, view)
True
The view will serve the file that it traverses to.
>>> view = view.publishTraverse(view.request, 'test.txt')
>>> print view()
Text file
It also sets the appropriate headers for cache control on the response.
>>> for name in sorted(view.request.response.headers):
... print "%s: %s" % (name, view.request.response.getHeader(name))
Cache-Control: public...
Content-Type: text/plain
Expires: ...
Last-Modified: ...
It accepts traversing to the file through an arbitrary revision
identifier.
>>> view = MyFolder(object(), FakeRequest(version="devel"))
>>> view = view.publishTraverse(view.request, 'rev6510')
>>> view = view.publishTraverse(view.request, 'image1.gif')
>>> print view()
GIF file
Requesting a directory raises a NotFound.
>>> view = MyFolder(object(), FakeRequest(version="devel"))
>>> view = view.publishTraverse(view.request, 'a_dir')
>>> view()
Traceback (most recent call last):
...
NotFound:...
By default, subdirectories are not exported. (See below on how to enable
this)
>>> view = MyFolder(object(), FakeRequest(version="devel"))
>>> view = view.publishTraverse(view.request, 'a_dir')
>>> view = view.publishTraverse(view.request, 'other.txt')
>>> view()
Traceback (most recent call last):
...
NotFound:...
Not requesting any file, also raises NotFound.
>>> view = MyFolder(object(), FakeRequest(version="devel"))
>>> view()
Traceback (most recent call last):
...
NotFound:...
As requesting a non-existent file.
>>> view = MyFolder(object(), FakeRequest(version="devel"))
>>> view = view.publishTraverse(view.request, 'image2')
>>> view()
Traceback (most recent call last):
...
NotFound:...
== ExportedImageFolder ==
For images, it's often convenient not to request the extension. There is
an ExportedImageFolder subclass, that will accept serving an image file
without extension. For example, requesting 'image1' or 'image2' will
serve the correct file. The supported extensions are defined in the
image_extensions property.
>>> from lp.app.browser.folder import ExportedImageFolder
>>> class MyImageFolder(ExportedImageFolder):
... folder = resource_dir
>>> view = MyImageFolder(object(), FakeRequest(version="devel"))
>>> view.image_extensions
('.png', '.gif')
>>> view = view.publishTraverse(view.request, 'image2')
>>> print view()
PNG file
>>> print view.request.response.getHeader('Content-Type')
image/png
If a file without extension exists, that one will be served.
>>> file(os.path.join(resource_dir, 'image3'), 'w').write(
... 'Image without extension')
>>> file(os.path.join(resource_dir, 'image3.gif'), 'w').write(
... 'Image with extension')
>>> view = MyImageFolder(object(), FakeRequest(version="devel"))
>>> view = view.publishTraverse(view.request, 'image3')
>>> print view()
Image without extension
>>> view = MyImageFolder(object(), FakeRequest(version="devel"))
>>> view = view.publishTraverse(view.request, 'image3.gif')
>>> print view()
Image with extension
== Exporting trees ==
By default ExportedFolder doesn't export contained folders, but if the
export_subdirectories is set to True, it will allow traversing to
subdirectories.
>>> os.mkdir(os.path.join(resource_dir, 'public'))
>>> file(os.path.join(
... resource_dir, 'public', 'test1.txt'), 'w').write('Public File')
>>> os.mkdir(os.path.join(resource_dir, 'public', 'subdir1'))
>>> file(os.path.join(
... resource_dir, 'public', 'subdir1', 'test1.txt'), 'w').write(
... 'Sub file 1')
>>> class MyTree(ExportedFolder):
... folder = resource_dir
... export_subdirectories = True
Traversing to a file in a subdirectory will now work.
>>> view = MyTree(object(), FakeRequest(version="devel"))
>>> view = view.publishTraverse(view.request, 'public')
>>> view = view.publishTraverse(view.request, 'subdir1')
>>> view = view.publishTraverse(view.request, 'test1.txt')
>>> print view()
Sub file 1
But traversing to the subdirectory itself will raise a NotFound.
>>> view = MyTree(object(), FakeRequest(version="devel"))
>>> view = view.publishTraverse(view.request, 'public')
>>> print view()
Traceback (most recent call last):
...
NotFound:...
Trying to request a non-existent file, will also raise a NotFound.
>>> view = MyTree(object(), FakeRequest(version="devel"))
>>> view = view.publishTraverse(view.request, 'public')
>>> view = view.publishTraverse(view.request, 'nosuchfile.txt')
>>> view()
Traceback (most recent call last):
...
NotFound:...
Traversing beyond an existing file to a non-existant file raises a
NotFound.
>>> view = MyTree(object(), FakeRequest(version="devel"))
>>> view = view.publishTraverse(view.request, 'public')
>>> view = view.publishTraverse(view.request, 'subdir1')
>>> view = view.publishTraverse(view.request, 'test1.txt')
>>> view = view.publishTraverse(view.request, 'nosuchpath')
>>> view()
Traceback (most recent call last):
...
NotFound:...
== Clean-up ==
>>> import shutil
>>> shutil.rmtree(resource_dir)
|