-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathTutorial.html
More file actions
350 lines (328 loc) · 20 KB
/
Copy pathTutorial.html
File metadata and controls
350 lines (328 loc) · 20 KB
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
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta charset="utf-8"/>
<meta name="generator" content="Docutils 0.13.1: http://docutils.sourceforge.net/" />
<title>Beginner Tutorial</title>
<link rel="stylesheet" href="../../Docs/Doc.css" type="text/css" />
</head>
<body>
<div class="document" id="beginner-tutorial">
<h1 class="title">Beginner Tutorial</h1>
<p>Webware for Python</p>
<dl class="field-list simple">
<dt>Version</dt>
<dd><p>X.Y</p>
</dd>
<dt>Released</dt>
<dd><p>@@/@@/@@</p>
</dd>
</dl>
<div class="contents topic" id="contents">
<p class="topic-title first">Contents</p>
<ul class="simple">
<li><p><a class="reference internal" href="#synopsis" id="id6">Synopsis</a></p></li>
<li><p><a class="reference internal" href="#installation" id="id7">Installation</a></p></li>
<li><p><a class="reference internal" href="#setting-up-your-environment" id="id8">Setting Up Your Environment</a></p>
<ul>
<li><p><a class="reference internal" href="#creating-a-working-directory" id="id9">Creating a Working Directory</a></p></li>
<li><p><a class="reference internal" href="#changing-webware-configuration" id="id10">Changing Webware Configuration</a></p></li>
</ul>
</li>
<li><p><a class="reference internal" href="#creating-and-understanding-the-servlet" id="id11">Creating and Understanding the Servlet</a></p></li>
<li><p><a class="reference internal" href="#a-brief-introduction-to-the-servlet" id="id12">A Brief Introduction to the Servlet</a></p></li>
<li><p><a class="reference internal" href="#a-photo-album" id="id13">A Photo Album</a></p>
<ul>
<li><p><a class="reference internal" href="#iteration-1-displaying-files" id="id14">Iteration 1: Displaying Files</a></p></li>
<li><p><a class="reference internal" href="#iteration-2-uploading-files" id="id15">Iteration 2: Uploading Files</a></p></li>
</ul>
</li>
</ul>
</div>
<div class="section" id="synopsis">
<h1>Synopsis</h1>
<p>We present a tutorial on making a Webware script, and some guidance on turning
that into a web application.</p>
</div>
<div class="section" id="installation">
<h1>Installation</h1>
<p>This document does not cover the basic installation. See the <a class="reference external" href="InstallGuide.html">Install Guide</a>
-- you should be able to open up the WebKit Examples context in you browser
before you continue. For the beginning, you can use the built-in HTTP server,
but in a production environment you should use a real web server like Apache.</p>
</div>
<div class="section" id="setting-up-your-environment">
<h1>Setting Up Your Environment</h1>
<div class="section" id="creating-a-working-directory">
<h2>Creating a Working Directory</h2>
<p>We'll first set up a directory dedicated to your application. Run this command:</p>
<pre class="literal-block">$ cd ~
$ python /path/to/Webware/bin/MakeAppWorkDir.py -c Context -l Lib \
--gitignore WebwareTest</pre>
<p>You'll now have a directory WebwareTest in your home directory (or of course
you can put it in some other location). Inside this directory will be several
subdirectories and a couple files. The only file we'll worry about is
<span class="docutils literal">AppServer</span> (or <span class="docutils literal">AppServer.bat</span> if you're on Windows). The directories of
interest are <span class="docutils literal">Context</span> (that you specified with <span class="docutils literal"><span class="pre">-C</span> context</span>) where you'll
be putting your servlets; <span class="docutils literal">Configs</span> that holds some configuration files;
and <span class="docutils literal">Lib</span> where you can put your non-servlet code.</p>
<p>For more information about the working directory and setting up the file
structure for your application, see <a class="reference external" href="ApplicationDevelopment.html">Application Development</a>.</p>
</div>
<div class="section" id="changing-webware-configuration">
<h2>Changing Webware Configuration</h2>
<p>For the most part the configuration is fine, but we'll make a couple changes to
make it easier to develop. For more information on configuration see the
<a class="reference external" href="Configuration.html">Configuration Guide</a>.</p>
<p>In the file <span class="docutils literal">AppServer.config</span>, change this line:</p>
<pre class="literal-block"># Original (default setting):
AutoReload = False
# To:
AutoReload = True</pre>
<p>This will cause the AppServer to restart if any loaded files are changed --
without this you may edit a file and your application won't see the updated
version until you manually restart the AppServer.</p>
<p>The other change you may want to make to allow you to use more interesting URLs.
In Application.config:</p>
<pre class="literal-block"># Original (default setting):
ExtraPathInfo = False
# To:
ExtraPathInfo = True</pre>
<p>Otherwise the settings should be appropriate for development.
(There are several setting you would want to change before deploying the
application in a production environment).</p>
</div>
</div>
<div class="section" id="creating-and-understanding-the-servlet">
<h1>Creating and Understanding the Servlet</h1>
<p>Webware's core concept for serving pages is the <em>servlet</em>. This is a class that
creates a response given a request.</p>
<p>The core classes to understanding the servlet are <span class="docutils literal">Servlet</span>, <span class="docutils literal">HTTPServlet</span>,
and <span class="docutils literal">Page</span>. Also of interest would be the request (<span class="docutils literal">Request</span> and
<span class="docutils literal">HTTPRequest</span>) and the response (<span class="docutils literal">Response</span> and <span class="docutils literal">HTTPResponse</span>)
-- the <span class="docutils literal">HTTP-</span> versions of these classes are more interesting.
There is also a <span class="docutils literal">Transaction</span> object, which is solely a container for the
request and response.</p>
<p>While there are several levels you can work on while creating your servlet,
in this tutorial we will work solely with subclassing the <span class="docutils literal">Page</span> class.
This class defines a more high-level interface, appropriate for generating HTML
(though it can be used with any content type). It also provides a number of
convenience methods.</p>
</div>
<div class="section" id="a-brief-introduction-to-the-servlet">
<h1>A Brief Introduction to the Servlet</h1>
<p>Each servlet is a plain Python class. There is no Webware magic (except perhaps
for the level one <em>import module based on URL</em> spell). <a class="reference external" href="../../PSP/Docs/index.html">PSP</a> has more magic,
but that's a topic for another day.</p>
<p>An extremely simple servlet might look like:</p>
<pre class="literal-block">from WebKit.Page import Page
class MyServlet(Page):
def title(self):
return 'My Sample Servlet'
def writeContent(self):
self.write('Hello world!')</pre>
<p>This would be placed in <span class="docutils literal">MyServlet.py</span>.
Webware will create a pool of <span class="docutils literal">MyServlet</span> instances, which will be reused.
Servlets "write" the text of the response (like <span class="docutils literal"><span class="pre">self.write("some</span> text")</span>).
Webware calls the servlet like this:</p>
<ul class="simple">
<li><p>An unused servlet is taken from the pool, or another servlet is created.</p></li>
<li><p><span class="docutils literal">awake(transaction)</span> is called. This is a good place to set up data for
your servlet. You can put information in instance variables for use later
on. But be warned -- those instance variables will hang around potentially
for a long time if you don't delete them later (in <span class="docutils literal">sleep</span>).</p></li>
<li><p>Several low-level methods are called, which Page isolates you from. We will
ignore these.</p></li>
<li><p><span class="docutils literal">writeHTML()</span> is called. <span class="docutils literal">Page</span> implements this just fine, but you can
override it if you want total control, or if you want to output something
other than HTML.</p></li>
<li><p><span class="docutils literal">writeDocType()</span> would write something like
<span class="docutils literal"><!DOCTYPE HTML PUBLIC <span class="pre">"-//W3C//DTD</span> HTML 4.01 <span class="pre">Transitional//EN"</span> <span class="pre">"http://www.w3.org/TR/html4/loose.dtd"></span></span> or simply <span class="docutils literal"><!DOCTYPE html></span>.</p></li>
<li><p>The <head> section of the page is written. <span class="docutils literal">title()</span> gives the title, and
you probably want to override it.</p></li>
<li><p><span class="docutils literal">writeStyleSheet()</span> is called, if you want to write that or anything else
in the <head> section.</p></li>
<li><p>The <body> tag is written. Have <span class="docutils literal">htBodyArgs()</span> return anything you want in
the <body> tag (like <span class="docutils literal"><span class="pre">onLoad="loadImages()"</span></span>).</p></li>
<li><p><span class="docutils literal">writeBodyParts()</span> is called, which you may want to override if you want
to create a template for other servlets.</p></li>
<li><p><span class="docutils literal">writeContent()</span> should write the main content for the page. This is where
you do most of your display work.</p></li>
<li><p>The response is packaged up, the headers put on the front, cookies handled,
and it's sent to the browser. This is all done for you.</p></li>
<li><p><span class="docutils literal">sleep(transaction)</span> is called. This is where you should clean up anything
you might have set up earlier -- open files, open database connections, etc.
Often it's empty. Note that <span class="docutils literal">sleep()</span> is called even if an exception was
raised at any point in the servlet processing, so it should (if necessary)
check that each resource was in fact acquired before trying to release it.</p></li>
<li><p>The servlet is placed back into the pool, to be used again. This only happens
after the transaction is complete -- the servlet won't get reused any earlier.</p></li>
</ul>
<p>You only have to override the portions that you want to. It is not uncommon to
only override the <span class="docutils literal">writeContent()</span> method in a servlet, for instance.</p>
<p>You'll notice a file <span class="docutils literal">Context/Main.py</span> in your working directory. You can look
at it to get a feel for what a servlet might look like. (As an aside, a servlet
called <span class="docutils literal">Main</span> or <span class="docutils literal">index</span> will be used analogous to the <span class="docutils literal">index.html</span> file).
You can look at it for a place to start experimenting, but here we'll work on
developing an entire (small) application, introducing the other concepts as we
go along.</p>
</div>
<div class="section" id="a-photo-album">
<h1>A Photo Album</h1>
<p>If you look online, you'll see a huge number of web applications available for
an online photo album. The world needs one more!</p>
<p>You will need the <a class="reference external" href="http://www.pythonware.com/products/pil/">Python Imaging Library</a> (PIL) installed for this example.
First we'll use it to find the sizes of the images, and later we will use it to
create thumbnails.</p>
<p>We'll develop the application in two iterations.</p>
<div class="section" id="iteration-1-displaying-files">
<h2>Iteration 1: Displaying Files</h2>
<p>For simplicity, we will store image files in a subdirectory <span class="docutils literal">Pics</span> of the
default context directory <span class="docutils literal">WebwareTest/Context</span> and let the AppServer deliver
the files. In a production environment, you would place the <span class="docutils literal">Pics</span> directory
outside of the context and let the web server deliver the files directly.</p>
<p>For the first iteration, we'll display files that you upload by hand to
the <span class="docutils literal">Pics</span> directory.</p>
<p>We do this with two servlets -- one servlet <span class="docutils literal">Main.py</span> to show the entire
album, and another <span class="docutils literal">View.py</span> for individual pictures. Place these two servlets
in the default context directory. First, <span class="docutils literal">Main.py</span>:</p>
<pre class="literal-block">from WebKit.Page import Page # the base class for web pages
import os
import Image # the Python Imaging Library
dir = os.path.join(os.path.dirname(__file__), 'Pics')
class Main(Page):
def title(self):
# It's nice to give a real title, otherwise "Main" would be used.
return 'Photo Album'
def writeContent(self):
# We'll format these simpy, one thumbnail per line:
for filename in os.listdir(dir):
im = Image.open(os.path.join(dir, filename))
x, y = im.size
# Here we figure out the scaled-down size of the image,
# so that we preserve the aspect ratio. We'll use fake
# thumbnails, where the image is scaled down by the browser.
x, y = x * 100 / y, 100
# Note that we are just using % substitution to generate
# the HTML. There's other ways, but this works well enough.
# We're linking to the View servlet which we'll show later.
# Notice we use urlEncode -- otherwise we'll encounter bugs if
# there's a file with an embedded space or other character in it.
url = self.urlEncode(filename)
self.writeln('<p><a href="View?filename=%s">'
'<img src="Pics/%s" width="%i" height="%i"></a></p>'
% (url, url, x, y))</pre>
<p>The servlet <span class="docutils literal">View</span> takes one URL parameter of <span class="docutils literal">filename</span>. You can get
the value of a URL parameter like <span class="docutils literal"><span class="pre">self.request().field('filename')</span></span> or,
if you want a default value, you can use <span class="docutils literal"><span class="pre">self.request().field('filename',</span> defaultValue)</span>. In the likely case you don't want to write <span class="docutils literal">self.request()</span>
before retrieving each value, do:</p>
<pre class="literal-block">req = self.request()
self.write(req.field('username'))
# Even more compactly:
field = self.request().field
self.write(field('username'))</pre>
<p>So here is our complete <span class="docutils literal">View</span> servlet:</p>
<pre class="literal-block">from WebKit.Page import Page
import os
import Image
dir = os.path.join(os.path.dirname(__file__), 'Pics')
class View(Page):
def title(self):
return 'View: %s' \
% self.htmlEncode(self.request().field('filename'))
def writeContent(self):
filename = self.request().field('filename')
im = Image.open(os.path.join(dir, filename))
self.writeln('<div style="text-align:center">')
self.writeln('<h4>%s</h4>' % filename)
self.writeln('<img src="Pics/%s" width="%i" height="%i">'
% (self.urlEncode(filename), im.size[0], im.size[1]))
self.writeln('<p><a href="Main">Return to Index</a></p>')
self.writeln('</div>')</pre>
</div>
<div class="section" id="iteration-2-uploading-files">
<h2>Iteration 2: Uploading Files</h2>
<p>That was fairly simple -- but usually you want to upload files, potentially
through a web interface. Along the way we'll add thumbnail generation using
PIL, and slightly improve the image index.</p>
<p>We'll generate thumbnails kind of on demand, so you can still upload files
manually -- thumbnails will be put in the directory <span class="docutils literal">Thumbs</span> and have <span class="docutils literal"><span class="pre">-tn</span></span>
appended to the name just to avoid confusion:</p>
<pre class="literal-block">from WebKit.Page import Page # the base page class
import os
import Image # the Python Imaging Library
baseDir = os.path.dirname(__file__)
picsDir = os.path.join(baseDir, 'Pics')
thumbsDir = os.path.join(baseDir, 'Thumbs')
class Main(Page):
def title(self):
return 'Photo Album'
def writeContent(self):
# The heading:
self.writeln('<h1 style="text-align:center">%s</h1>' % self.title())
# We'll format these in a table, two columns wide
self.writeln('<table width="100%">')
col = 0 # will be 0 for the left and 1 for the right column
filenames = os.listdir(picsDir)
# We'll sort the files, case-insensitive
filenames.sort(key=lambda filename: filename.lower())
for filename in filenames:
if not col: # left column
self.write('<tr style="text-align:center">')
thumbFilename = os.path.splitext(filename)
thumbFilename = '%s-tn%s' % thumbFilename
if not os.path.exists(os.path.join(thumbsDir, thumbFilename)):
# No thumbnail exists -- we have to generate one
if not os.path.exists(thumbsDir):
# Not even the Thumbs directory exists -- make it
os.mkdir(thumbsDir)
im = Image.open(os.path.join(picsDir, filename))
im.thumbnail((250, 100))
im.save(os.path.join(thumbsDir, thumbFilename))
else:
im = Image.open(os.path.join(thumbsDir, thumbFilename))
url = self.urlEncode(filename)
self.writeln('<td><p><a href="View?filename=%s">'
'<img src="Pics/%s" width="%i" height="%i"></a></p>'
'<p>Filename: %s<br>Size: %i Bytes</p>'
% (url, url, im.size[0], im.size[1], filename,
os.stat(os.path.join(picsDir, filename)).st_size))
if col: # right column
self.writeln('</tr>')
col = not col
self.write('</table>')
self.write('<p style="text-align:center">'
'<a href="Upload">Upload an image</a></p>')</pre>
<p>The <span class="docutils literal">View</span> servlet we'll leave just like it was.</p>
<p>We'll add an <span class="docutils literal">Upload</span> servlet. Notice we use <span class="docutils literal"><span class="pre">enctype="multipart/form-data"</span></span>
in the <span class="docutils literal"><form></span> tag -- this is an HTMLism for file uploading (otherwise
you'll just get the filename and not the file contents). Finally, when the form
is finished and we have uploaded the image, we redirect them to the viewing
page by using <span class="docutils literal"><span class="pre">self.response().sendRedirect(url)</span></span>:</p>
<pre class="literal-block">from WebKit.Page import Page
import os
dir = os.path.join(os.path.dirname(__file__), 'Pics')
class Upload(Page):
def writeContent(self):
if self.request().hasField('imageFile'):
self.doUpload()
return
self.writeln('''
<h3>Upload your image:</h3>
<form action="Upload" method="post" enctype="multipart/form-data">
<input type="file" name="imageFile">
<input type="submit" value="Upload">
</form>''')
def doUpload(self):
file = self.request().field('imageFile')
# Because it's a file upload, we don't get a string back.
# So to get the value we do this:
filename, contents = file.filename, file.value
open(os.path.join(dir, filename), 'wb').write(contents)
url = 'View?filename=' + self.urlEncode(filename)
self.response().sendRedirect(url)</pre>
</div>
</div>
</div>
</body>
</html>