Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 767 lines (523 sloc) 27.702 kb
0cd9170 edit
Markus authored
1 ---
2 layout: default
3 title: web.py 0.3 tutorial
4 ---
5
6 # web.py 0.3 tutorial
7
8 This is a work-in-progress
9
fd39283 edit
Markus authored
10
57817c2 edit
Markus authored
11 ## TODO: app.internalerror = web.debugerror
25f026c edit
Markus authored
12
2a66ed6 edit
Markus authored
13 ## TODO: '$user' vs. '$:user' and '$var user:$user' vs. '$var user:$user\'
14
15 ## TODO: Multiple submit buttons
16
ffc2285 edit
Markus authored
17 ## TODO: Show/hide complete code at the end of sections
fd39283 edit
Markus authored
18
1c2e566 edit
Markus authored
19 TODO: move the next paragraph over to install?
0cd9170 edit
Markus authored
20
75a4a2c change web.loader to autoreload=True
Davy authored
21 To create a website with web.py you need to know the Python programming language and have it installed. Installation instructions for Python can be found at [http://python.org/](http://python.org/) .If you don't know if Python is installed on your system, open a terminal and type `python`. A great starting point to learn Python is the official [tutorial] (http://docs.python.org/tut/tut.html). If you are new to programming in general, [Think Python] (http://www.greenteapress.com/thinkpython/) is a wonderful book to understand key concepts in programming.
0cd9170 edit
Markus authored
22
1c2e566 edit
Markus authored
23 ## TOC
24
25 TODO: ...
26
fd39283 edit
Markus authored
27
0cd9170 edit
Markus authored
28 ## Prerequisites
29
30 This tutorial assumes that both Python and web.py are installed on your system. If this is not the case, please follow the [installation instructions] (http://webpy.org/install) before you continue.
31
0872f0f edit
Markus authored
32 Furthermore, basic HTML knowledge is needed to understand some examples.
33
fd39283 edit
Markus authored
34
0cd9170 edit
Markus authored
35 ## Hello Web in web.py
36
37 Open your favorite text editor and create a new file `hello.py`. In this file you will define the content and logic of your web application as well as its web addresses (URLs).
38
39 Before you are able to use the tools web.py provides, you need to import the web.py module with the following code:
40
41 import web
42
57817c2 edit
Markus authored
43 In web.py web pages are mapped to Python classes. Let's create the code for the first page which is here called `hello`:
0cd9170 edit
Markus authored
44
57817c2 edit
Markus authored
45 class hello:
0cd9170 edit
Markus authored
46 def GET(self):
47 return "Hello, Web!"
48
57817c2 edit
Markus authored
49 The `hello` class has a function named `GET` which returns "Hello, Web!". Why `GET`?
0cd9170 edit
Markus authored
50
51 When you open a web page, your browser asks for the content of that page. This request is called the `GET` method. web.py uses the same terminology. The string your `GET` method returns is displayed in your browser.
52
53 Although the code for your first page is written, it cannot yet be opened in a browser. Let's proceed with mapping a web address (URL) to your class. Insert the following code after the import statement:
54
55 urls = (
57817c2 edit
Markus authored
56 '/', 'hello')
0cd9170 edit
Markus authored
57
57817c2 edit
Markus authored
58 This tells web.py to map the root of your website (like http://webpy.org/) to your Python class named `hello`.
0cd9170 edit
Markus authored
59
60 Next create an instance of a web.py application. This instance will be the mediator between your classes and the web. It will handle browser requests and serve your pages. (In short: It will do everything that you really don't want to care about.) Use the following code:
61
62 app = web.application(urls, globals())
63
57817c2 edit
Markus authored
64 Note that `web.application()` gets called with two arguments. Your URL mapping (`urls`) and your global namespace which contains your `hello` class (`globals()`).
0cd9170 edit
Markus authored
65
66 To finish your web.py application insert the following code at the end of your code:
67
68 if __name__ == "__main__":
69 app.run()
70
71 `app.run()` starts the web application to serve requested pages.
72
1c2e566 edit
Markus authored
73 ### Complete code
74
75 hello.py
0cd9170 edit
Markus authored
76
77 import web
78
79 urls = (
57817c2 edit
Markus authored
80 '/', 'hello')
0cd9170 edit
Markus authored
81
82 app = web.application(urls, globals())
83
57817c2 edit
Markus authored
84 class hello:
0cd9170 edit
Markus authored
85 def GET(self):
86 return 'Hello, web!'
87
88 if __name__ == "__main__":
89 app.run()
90
91 Save the file and run the following command to start your application:
92
93 python hello.py
94
95 The first output of your application is the address of your web site. By default this is:
96
4a9ee06 @kiloforce minor errors
kiloforce authored
97 http://locahost:8080/
0cd9170 edit
Markus authored
98
99 Open this address with your web browser. That's it. Congrats! You can stop your application at any time by pressing `ctrl+c` in the terminal.
100
101 Note: You can also visit your site at `http://localhost:8080/`
102
fd39283 edit
Markus authored
103
0cd9170 edit
Markus authored
104 ## Having multiple pages
105
106 In this part you will learn how to manage multiple pages. Let's add another class to your 'Hello Web' application:
107
57817c2 edit
Markus authored
108 class bye:
0cd9170 edit
Markus authored
109 def GET(self):
110 return 'Bye, web!'
111
112 As mentioned above, each page needs a unique address. Modify your list of URLs as follows:
113
114 urls = (
57817c2 edit
Markus authored
115 '/', 'hello',
4a9ee06 @kiloforce minor errors
kiloforce authored
116 '/bye', 'bye')
0cd9170 edit
Markus authored
117
57817c2 edit
Markus authored
118 This will make your class `bye` respond to requests at `/bye/`. Now start your application and open `http://localhost:8080/bye/` in your browser.
0cd9170 edit
Markus authored
119
120 Note: Currently you need to restart your application to see any changes. Try to pass a third argument to `web.application` and restart your application:
121
7f2356a @henrikno reloader module has changed
henrikno authored
122 app = web.application(urls, globals(), True)
0cd9170 edit
Markus authored
123
ffc2285 edit
Markus authored
124
0cd9170 edit
Markus authored
125 Future changes can now be seen instantly, although you might need to reload a page in your browser.
126
1c2e566 edit
Markus authored
127 ### Complete code
128
129 hello.py
ffc2285 edit
Markus authored
130
131 import web
132
133 urls = (
57817c2 edit
Markus authored
134 '/', 'hello',
4a9ee06 @kiloforce minor errors
kiloforce authored
135 '/bye', 'bye')
ffc2285 edit
Markus authored
136
4a9ee06 @kiloforce minor errors
kiloforce authored
137 app = web.application(urls, globals(), True)
ffc2285 edit
Markus authored
138
57817c2 edit
Markus authored
139 class hello:
ffc2285 edit
Markus authored
140 def GET(self):
4a9ee06 @kiloforce minor errors
kiloforce authored
141 return 'Hello, web!'
ffc2285 edit
Markus authored
142
57817c2 edit
Markus authored
143 class bye:
ffc2285 edit
Markus authored
144 def GET(self):
145 return 'Bye, web!'
146
147 if __name__ == "__main__":
148 app.run()
149
fd39283 edit
Markus authored
150
ae3b4f6 Dynamic pages; web.input; url parsing
Josh Goldfoot authored
151 ## Dynamic pages
ffc2285 edit
Markus authored
152
ae3b4f6 Dynamic pages; web.input; url parsing
Josh Goldfoot authored
153 The examples shown above are simple; all it does it display the same message, every time. Suppose you want your app to greet people by name?
ffc2285 edit
Markus authored
154
ae3b4f6 Dynamic pages; web.input; url parsing
Josh Goldfoot authored
155 ### A dynamic "Hello, world!" -- using GET and POST variables
ffc2285 edit
Markus authored
156
ae3b4f6 Dynamic pages; web.input; url parsing
Josh Goldfoot authored
157 Change the "hello" class above so it looks like this:
ffc2285 edit
Markus authored
158
57817c2 edit
Markus authored
159 class hello:
ffc2285 edit
Markus authored
160 def GET(self):
ae3b4f6 Dynamic pages; web.input; url parsing
Josh Goldfoot authored
161 i = web.input(name = 'web')
162 return 'Hello, ' + web.websafe(i.name) + '!'
163
4a9ee06 @kiloforce minor errors
kiloforce authored
164 Run the script, and then go to http://localhost:8080/?name=Luke . You should see "Hello, Luke!."
ffc2285 edit
Markus authored
165
ae3b4f6 Dynamic pages; web.input; url parsing
Josh Goldfoot authored
166 Here is what is happening:
ffc2285 edit
Markus authored
167
ae3b4f6 Dynamic pages; web.input; url parsing
Josh Goldfoot authored
168 When you add the "?name=Luke" to the end of your web request, you are passing the variable "name" to the web server, with a value of "Luke."
1c2e566 edit
Markus authored
169
ae3b4f6 Dynamic pages; web.input; url parsing
Josh Goldfoot authored
170 The line `i = web.input(name = "web")` creates a Storage object (a fancy type of Python dictionary) that contains all variables passed into it. You can also do this by just calling `i = web.input()`. Here, by putting `name = 'web'` into the call, we tell it to use the string "web" as a default, in case the user didn't pass in a "name" variable at all.
171
172 We read the "name" value from i by just saying `i.name`. It's also possible to do `i['name']`; use the syntax you prefer.
173
174 Finally, we pass i.name through the `web.websafe` function before returning it to the user. If your page is being served as HTML, rather than text, then this is an important security step to protect against [cross-site scripting attacks](http://en.wikipedia.org/wiki/Cross-site_scripting). (As we will see, web.py's form templates offer built-in protection to those attacks).
175
176 ### Another dynamic "Hello, world!" -- parsing URL strings
177
178 Making the user type in something like "http://localhost:8080/?name=Luke" is so 1996; wouldn't it be nicer if we could just get the user to enter "http://localhost:8080/Luke"?
179
180 Try this code:
ffc2285 edit
Markus authored
181
182 import web
183
184 urls = (
ae3b4f6 Dynamic pages; web.input; url parsing
Josh Goldfoot authored
185 '/(.*)', 'hello')
ffc2285 edit
Markus authored
186
ae3b4f6 Dynamic pages; web.input; url parsing
Josh Goldfoot authored
187 app = web.application(urls, globals())
ffc2285 edit
Markus authored
188
57817c2 edit
Markus authored
189 class hello:
ae3b4f6 Dynamic pages; web.input; url parsing
Josh Goldfoot authored
190 def GET(self, name):
191 return 'Hello, ' + web.websafe(name) + '!'
ffc2285 edit
Markus authored
192
193 if __name__ == "__main__":
194 app.run()
195
ae3b4f6 Dynamic pages; web.input; url parsing
Josh Goldfoot authored
196 Notice that two things have changed:
197
198 1. The urls has a "(.*)" thing in it
199 2. The `GET` method now takes two parameters
200 3. There's no more `web.input` call.
201
202 As you can see, web.py is parsing the URL for you, based on a [regular expression](http://docs.python.org/lib/re-syntax.html) that you provide in the `urls` tuple.
fd39283 edit
Markus authored
203
ffc2285 edit
Markus authored
204 ## HTML in Python
205
57817c2 edit
Markus authored
206 Until now your classes returned only simple strings. Let's add some HTML. This can be done directly from inside your `hello.py`. Replace your class `hello` with this code:
ffc2285 edit
Markus authored
207
57817c2 edit
Markus authored
208 class hello:
ffc2285 edit
Markus authored
209 def GET(self):
210 return """<html>
211 <head>
212 <title>Hello, web!</title>
213 </head>
214 <body>
215 <h1>web.py</h1>
216 <p>Think about the <em>ideal</em> way to write a web app. Write the code to <b>make it happen</b>.</p>
217 </body>
218 </html>"""
219
220 Note that your page now has a custom title and HTML formatted content.
221
1c2e566 edit
Markus authored
222 ### Complete code
223
224 hello.py
ffc2285 edit
Markus authored
225
226 import web
227
228 urls = (
57817c2 edit
Markus authored
229 '/', 'hello',
4a9ee06 @kiloforce minor errors
kiloforce authored
230 '/bye', 'bye')
ffc2285 edit
Markus authored
231
4a9ee06 @kiloforce minor errors
kiloforce authored
232 app = web.application(urls, globals(), True)
ffc2285 edit
Markus authored
233
57817c2 edit
Markus authored
234 class hello:
ffc2285 edit
Markus authored
235 def GET(self):
236 return """<html>
237 <head>
238 <title>Hello, web!</title>
239 </head>
240 <body>
241 <h1>web.py</h1>
242 <p>Think about the <em>ideal</em> way to write a web app. Write the code to <b>make it happen</b>.</p>
243 </body>
244 </html>"""
245
246 if __name__ == "__main__":
247 app.run()
248
fd39283 edit
Markus authored
249
166cf1d edit
Markus authored
250
1c2e566 edit
Markus authored
251 ## HTML with site layout templates
252
253 Imagine a larger site with many pages. If all HTML for these pages is embedded into your Python code, things get messy and your code unmaintainable. Also reusing parts of your HTML code for other pages would be difficult. Therefore web.py lets you define site layout templates that can be shared between your pages.
254
166cf1d edit
Markus authored
255 First create a directory `templates` next to your `hello.py` file. Create a file `hello.html` and save it in `templates`. This file will contain the HTML markup that is used to render your page. Start with the following basic template:
1c2e566 edit
Markus authored
256
bf9bf4a edit
Markus authored
257 $def with (title, name, content)
1c2e566 edit
Markus authored
258 <html>
259 <head>
166cf1d edit
Markus authored
260 <title>$title</title>
1c2e566 edit
Markus authored
261 </head>
262 <body>
bf9bf4a edit
Markus authored
263 <p>You are visiting page <b>$name</b>.</p>
166cf1d edit
Markus authored
264 <p>$content</p>
1c2e566 edit
Markus authored
265 </body>
266 </html>
267
166cf1d edit
Markus authored
268 Create a second template `bye.html`:
1c2e566 edit
Markus authored
269
bf9bf4a edit
Markus authored
270 $def with (title, name, *numbers)
166cf1d edit
Markus authored
271 <html>
272 <head>
273 <title>$title</title>
274 </head>
275 <body>
bf9bf4a edit
Markus authored
276 <p>You are visiting page <b>$name</b>.</p>
fe1f563 edit
Markus authored
277 <p>Find the answer to all questions below:
278 $for number in numbers:
279 <p>$number</p>
166cf1d edit
Markus authored
280 </body>
281 </html>
282
bf9bf4a edit
Markus authored
283 Besides defining a page structure, these templates will use variables. The first line of `hello.html` (`def with (title, name, number)`) will tell web.py that this template needs to be called with three arguments. Wherever `$title` is used in the template the actual value of `title` is inserted.
166cf1d edit
Markus authored
284
bf9bf4a edit
Markus authored
285 Arguments of `bye.html` are `title`, `name` and an arbitrary number of numbers (`*numbers`). All arguments beside `title` and `name` are put into the list `numbers`. This list is then iterated (`$for number in numbers:`) and each number (`number`) is written in its own paragraph. You see that `$` is not only used to access template variables but also to evaluate (safe) Python code like `for` loops or `if` statements.
fe1f563 edit
Markus authored
286
287 Now insert the following line before your class definitions to create a so called template renderer. The location of your templates is passed to the renderer as an argument:
166cf1d edit
Markus authored
288
289 render = web.template.render('templates/')
290
291 Next modify your classes to render your pages using the two different templates:
1c2e566 edit
Markus authored
292
57817c2 edit
Markus authored
293 class hello:
1c2e566 edit
Markus authored
294 def GET(self):
166cf1d edit
Markus authored
295 return render.hello("Templates demo", "Hello", "A long time ago...")
1c2e566 edit
Markus authored
296
57817c2 edit
Markus authored
297 class bye:
1c2e566 edit
Markus authored
298 def GET(self):
fe1f563 edit
Markus authored
299 return render.bye("Templates demo", "Bye", "14", "8", "25", "42", "19")
166cf1d edit
Markus authored
300
301 Open the pages in your browser. web.py fetches your templates and dynamically inserts the values that you passed to your templates.
ffc2285 edit
Markus authored
302
1c2e566 edit
Markus authored
303 ### Complete code
ffc2285 edit
Markus authored
304
1c2e566 edit
Markus authored
305 hello.py
fd39283 edit
Markus authored
306
1c2e566 edit
Markus authored
307 import web
308
309 urls = (
57817c2 edit
Markus authored
310 '/', 'hello',
311 '/bye/', 'bye')
1c2e566 edit
Markus authored
312
4a9ee06 @kiloforce minor errors
kiloforce authored
313 app = web.application(urls, globals(), True)
1c2e566 edit
Markus authored
314
315 render = web.template.render('templates/')
316
57817c2 edit
Markus authored
317 class hello:
1c2e566 edit
Markus authored
318 def GET(self):
4a9ee06 @kiloforce minor errors
kiloforce authored
319 return render.hello("Templates demo", "Hello", "A long time ago...")
1c2e566 edit
Markus authored
320
57817c2 edit
Markus authored
321 class bye:
1c2e566 edit
Markus authored
322 def GET(self):
fe1f563 edit
Markus authored
323 return render.bye("Templates demo", "Bye", "14", "8", "25", "42", "19")
324
1c2e566 edit
Markus authored
325 if __name__ == "__main__":
326 app.run()
327
166cf1d edit
Markus authored
328 templates/hello.html
1c2e566 edit
Markus authored
329
bf9bf4a edit
Markus authored
330 $def with (title, name, content)
1c2e566 edit
Markus authored
331 <html>
332 <head>
166cf1d edit
Markus authored
333 <title>$title</title>
1c2e566 edit
Markus authored
334 </head>
335 <body>
bf9bf4a edit
Markus authored
336 <p>You are visiting page <b>$name</b>.</p>
166cf1d edit
Markus authored
337 <p>$content</p>
1c2e566 edit
Markus authored
338 </body>
339 </html>
340
166cf1d edit
Markus authored
341 templates/bye.html
342
bf9bf4a edit
Markus authored
343 $def with (title, name, *numbers)
166cf1d edit
Markus authored
344 <html>
345 <head>
346 <title>$title</title>
347 </head>
348 <body>
bf9bf4a edit
Markus authored
349 <p>You are visiting page <b>$name</b>.</p>
fe1f563 edit
Markus authored
350 <p>Find the answer to all questions below:
351 $for number in numbers:
352 <p>$number</p>
166cf1d edit
Markus authored
353 </body>
354 </html>
355
356
357 ## Using a base layout (template inheritance)
358
359 The previous example defined two templates but both had duplicate code. In most cases your pages share a lot of common code like a navigation bar or a footer. Let's create a file `base.html` which contains all the code your pages share with each other:
360
bf9bf4a edit
Markus authored
361 $def with (page)
362 <html>
363 <head>
364 <title>$page.title</title>
365 </head>
366 <body>
367 <p>You are visiting page <b>$page.name</b>.</p>
368 $:page
369 </body>
370 </html>
371
372 This base template receives only one variable `page`. `$page.title` is a placeholder for a variable named `title` defined in a child template. `$:page` is a placeholder for everything else that you put in your child template. Modify `hello.html` to be a child template:
373
374 $def with (title, name, content)
375 $var title:$title
376 $var name:$name
377 <p>$content</p>
378
379 The previously duplicated code for the HTML body, the page title and the current page information is gone. Instead `$var title:$title` tells the base template to use the local `title` as `$page.title`. The remaining line `<p>$content</p>` will be available in the base template as `$:page`.
380
381 Modify `bye.html` accordingly:
382
383 $def with (title, name, *numbers)
384 $var title:$title
385 $var name:$name
386 <p>Find the answer to all questions below:</p>
387 $for number in numbers:
388 <p>$number</p>
389
390 The last step is to tell web.py to use `base.html` as the base template. Use the following code (you might need to replace your previous code):
391
392 render = web.template.render('templates/', base='base')
393
394 Both `hello.html` and `bye.html` will now use `base.html`.
395
396 ### Complete code
397
398 hello.py
399
400 import web
401
402 urls = (
57817c2 edit
Markus authored
403 '/', 'hello',
404 '/bye/', 'bye')
bf9bf4a edit
Markus authored
405
75a4a2c change web.loader to autoreload=True
Davy authored
406 app = web.application(urls, globals(), autoreload=True)
bf9bf4a edit
Markus authored
407
408 render = web.template.render('templates/', base='base')
409
57817c2 edit
Markus authored
410 class hello:
bf9bf4a edit
Markus authored
411 def GET(self):
412 return render.hello("Templates demo", "Hello", "A long time ago...")
413
57817c2 edit
Markus authored
414 class bye:
bf9bf4a edit
Markus authored
415 def GET(self):
416 return render.bye("Templates demo", "Bye", "14", "8", "25", "42", "19")
417
418 if __name__ == "__main__":
419 app.run()
420
421 base.html
422
423 $def with (page)
424 <html>
425 <head>
426 <title>$page.title</title>
427 </head>
428 <body>
429 <p>You are visiting page <b>$page.name</b>.</p>
430 $:page
431 </body>
432 </html>
433
434 hello.html
435
436 $def with (title, name, content)
437 $var title:$title
438 $var name:$name
439 <p>$content</p>
440
441 bye.html
442
443 $def with (title, name, *numbers)
444 $var title:$title
445 $var name:$name
446 <p>Find the answer to all questions below:</p>
447 $for number in numbers:
448 <p>$number</p>
166cf1d edit
Markus authored
449
450
fd39283 edit
Markus authored
451 ## Static content
452
453 Now that your application serves HTML formatted content, you probably want to include static files like images or css style files. To achieve this create a directeory called `static` next to your `hello.py` file. Put a picture file (here called `logo.png`) in your `static` directory. Then include the file on your page:
454
57817c2 edit
Markus authored
455 class hello:
fd39283 edit
Markus authored
456 def GET(self):
457 return """<img src="./static/logo.png">"""
458
1c2e566 edit
Markus authored
459 ### Complete code
460
461 hello.py
fd39283 edit
Markus authored
462
463 import web
464
465 urls = (
57817c2 edit
Markus authored
466 '/', 'hello')
fd39283 edit
Markus authored
467
468 app = web.application(urls, globals(), web.reloader)
469
57817c2 edit
Markus authored
470 class hello:
fd39283 edit
Markus authored
471 def GET(self):
472 return """<img src="./static/logo.png">"""
473
474 if __name__ == "__main__":
bf9bf4a edit
Markus authored
475 app.run()
476
477
6c39875 edit
Markus authored
478 ## User input (HTML forms and the `POST` method) [cookbook] (http://webpy.org/form)
0872f0f edit
Markus authored
479
480 Until now `GET` functions were introduced to serve pages but there was no way a user could send data back to your application. A function called `POST` will allow this. To use `POST` you need to create form fields on your page where a user can input his data. Let's make `hello.py` return a page that contains HTML forms using web.py `form` module. To reduce typing add the following import statement:
481
482 from web import form
483
6c39875 edit
Markus authored
484 Now define a form before your classes in `hello.py`. This example only uses a single input field. Visit the [cookbook] (http://webpy.org/form) for more advanced types. The following code gives you a text box with validation of the input:
0872f0f edit
Markus authored
485
25f026c edit
Markus authored
486 number_form = form.Form(
6c39875 edit
Markus authored
487 form.Textbox('number',
488 form.notnull,
25f026c edit
Markus authored
489 form.regexp('^-?\d+$', 'Not a number.'),
d714a89 edit
Josh Goldfoot authored
490 form.Validator('Not greater than 10.', lambda x: int(x)>10),
491 description='Enter a number greater than 10:'
6c39875 edit
Markus authored
492 ))
493
494 `form.Textbox()` creates an HTML text box. The first parameter specifies its name: `'number'`. Most often you will want to validate the input of a user instantly and allow him to correct errors. `form.notnull` makes it a required field that cannot be left empty. `form.regexp()` matches the input with the given regular expression. Here it is checked if the input is a number. `form.Validator()` additionally checks if the input is a number greater ten. And finally, `description` is the text that is printed in front of the text box.
495
496 Now make your template `hello.html` accept and display a form:
497
498 $def with (form)
499 <form name="test" method="POST">
500 $if not form.valid: <p>Sorry, your input was invalid.</p>
501 $:form.render()
502 <input type="submit" value="Check" />
503 </form>
504
505 Notice that the template will print an error message if the form input is invalid.
506
57817c2 edit
Markus authored
507 And finally your `hello` class needs the following `GET` and `POST` methods:
6c39875 edit
Markus authored
508
57817c2 edit
Markus authored
509 class hello:
6c39875 edit
Markus authored
510 def GET(self):
25f026c edit
Markus authored
511 my_form = number_form()
512 return render.hello(my_form)
6c39875 edit
Markus authored
513
514 def POST(self):
25f026c edit
Markus authored
515 my_form = number_form()
516 if not my_form.validates():
517 return render.hello(my_form)
6c39875 edit
Markus authored
518 else:
25f026c edit
Markus authored
519 number = my_form['number'].value
520 if int(number) % 2:
6c39875 edit
Markus authored
521 return "Your number %s is odd." % number
25f026c edit
Markus authored
522 else:
523 return "Your number %s is even." % number
6c39875 edit
Markus authored
524
57817c2 edit
Markus authored
525 When you visit `hello` in your browser, the `GET` method creates an instance of your form and returns the rendered page. Enter a number greater 10 and press the `Check` button. Now the `POST` method is invoked to process your input. Because the `GET` and `POST` methods cannot access the same form instance a new one is created. `form.validates()` checks the input you entered. But how does it know what you have entered? By default the `validates()` method fetches your input from `web.input()` where it is stored as soon as you press the `Check` button. In case your input is invalid, the form is returned again. Else `my_form['number'].value` is retrieved which is the number you entered and your application will tell you if you entered an even or odd number.
90cbe66 edit
Markus authored
526
2a66ed6 edit
Markus authored
527 Note: You cannot access form values before having validated the form!
528
90cbe66 edit
Markus authored
529 Now go back and try some invalid input. First leave the text field blank and press `Check`. You will be informed that you left a required field blank. Enter some text and you will get a "Not a number" message. This is due to the regular expression check. And finally try some number that is not greater than ten. The form input will not be validated and you are advised to enter a number greater ten.
6c39875 edit
Markus authored
530
531 ### Complete code
532
533 hello.py
534
535 import web
536 from web import form
537
538 urls = (
57817c2 edit
Markus authored
539 '/', 'hello')
6c39875 edit
Markus authored
540
541 app = web.application(urls, globals(), web.reloader)
542 render = web.template.render('templates/')
543
25f026c edit
Markus authored
544 number_form = form.Form(
6c39875 edit
Markus authored
545 form.Textbox('number',
546 form.notnull,
25f026c edit
Markus authored
547 form.regexp('^-?\d+$', 'Not a number.'),
6c39875 edit
Markus authored
548 form.Validator('Not greater 10.', lambda x: int(x)>10),
549 description='Enter a number greater 10:'
550 ))
551
57817c2 edit
Markus authored
552 class hello:
6c39875 edit
Markus authored
553 def GET(self):
25f026c edit
Markus authored
554 my_form = number_form()
555 return render.hello(my_form)
6c39875 edit
Markus authored
556
557 def POST(self):
25f026c edit
Markus authored
558 my_form = number_form()
559 if not my_form.validates():
560 return render.hello(my_form)
6c39875 edit
Markus authored
561 else:
25f026c edit
Markus authored
562 number = my_form['number'].value
6c39875 edit
Markus authored
563 if int(number) % 2:
564 return "Your number %s is odd." % number
565 else:
566 return "Your number %s is even." % number
567
568 if __name__ == "__main__":
569 app.run()
570
571 hello.html
572
25f026c edit
Markus authored
573 $def with (form)
574 <form name="test" method="POST">
575 $if not form.valid: <p>Sorry, your input was invalid.</p>
576 $:form.render()
577 <input type="submit" value="Check" />
578 </form>
0872f0f edit
Markus authored
579
580
6c61c00 edit
Markus authored
581 ## Sessions [cookbook] (http://webpy.org/cookbook/sessions)
582
c7454db edit
Markus authored
583 ATTENTION: Sessions cannot be used with `web.reloader` at the time of this writing! It is a known bug.
6c61c00 edit
Markus authored
584
585 Many sites need to distinguish its visitors. Imagine you want to show the user the number of pages he visited on your page. Each visitor has a unique number. To allow separate tracking web.py uses so called sessions. Each visitor gets his very own session object in which his unique number is saved. First create a session object in `hello.py`. Put this line after your `app` is initialized:
586
587 session = web.session.Session(app, web.session.DiskStore('sessions'), initializer={'count': 0})
588
589 This creates a session object. The first parameter is simply the application the session object is used for. `web.session.DiskStore('sessions')` tells web.py to store sessions on disk (database storage is possible as well, see this [cookbook] (http://webpy.org/cookbook/sessions) entry). The third optional parameter initializes the session data dictionary for each user. Here each session object starts with zero visited pages (`count`). web.py creates a directory `sessions` to store session data on disk. Modify your classes in `hello.py` like this:
590
57817c2 edit
Markus authored
591 class hello:
6c61c00 edit
Markus authored
592 def GET(self):
593 session.count += 1
594 return "You visited " + str(session.count) + " pages."
595
57817c2 edit
Markus authored
596 class bye:
6c61c00 edit
Markus authored
597 def GET(self):
598 session.kill()
599 return ("Bye, web!")
600
57817c2 edit
Markus authored
601 Each time you visit `hello`, the number of pages you visited is incremented (`session.count += 1`). If you visit `bye` the session is killed (`session.kill()`). The next time you visit `hello`, a new session will be created and the counter will be zero again.
6c61c00 edit
Markus authored
602
0872f0f edit
Markus authored
603 ### Complete code
604
605 hello.py
606
607 import web
34f3e55 Prevent the known session debug error
denethor authored
608 web.config.debug=False
0872f0f edit
Markus authored
609
610 urls = (
57817c2 edit
Markus authored
611 '/', 'hello',
612 '/bye/', 'bye')
34f3e55 Prevent the known session debug error
denethor authored
613
614
0872f0f edit
Markus authored
615 app = web.application(urls, globals())
57817c2 edit
Markus authored
616 session = web.session.Session(app, web.session.DiskStore('sessions'),
617 initializer={'count': 0})
0872f0f edit
Markus authored
618
57817c2 edit
Markus authored
619 class hello:
0872f0f edit
Markus authored
620 def GET(self):
621 session.count += 1
622 return "You visited " + str(session.count) + " pages."
623
57817c2 edit
Markus authored
624 class bye:
0872f0f edit
Markus authored
625 def GET(self):
626 session.kill()
627 return ("Bye, web!")
628
629 if __name__ == "__main__":
630 app.run()
631
632
bf9bf4a edit
Markus authored
633 ## User authentication
634
87e5cba secure authentication
Josh Goldfoot authored
635 This is an example of user authentication using a session cookie, the most common method. Most often user authentication is done by providing functions to login and logout a user. Additionally, users often are able to register or delete their account.
0872f0f edit
Markus authored
636
57817c2 edit
Markus authored
637 ### Complete code
0872f0f edit
Markus authored
638
57817c2 edit
Markus authored
639 hello.py
640
641 import web
642 from web import form
643
87e5cba secure authentication
Josh Goldfoot authored
644 import random
645 from hashlib import sha1
646
647 # A simple user object that doesn't store passwords in plain text
648 # see http://en.wikipedia.org/wiki/Salt_(cryptography)
649 class PasswordHash(object):
650 def __init__(self, password_):
8e564a4 different salt
Josh Goldfoot authored
651 self.salt = "".join(chr(random.randint(33,127)) for x in xrange(64))
87e5cba secure authentication
Josh Goldfoot authored
652 self.saltedpw = sha1(password_ + self.salt).hexdigest()
653 def check_password(self, password_):
654 """checks if the password is correct"""
655 return self.saltedpw == sha1(password_ + self.salt).hexdigest()
656
657 # Note: a secure application would never store passwords in plaintext in the source code
658 users = {
659 'Kermit' : PasswordHash('frog'),
660 'ET' : PasswordHash('eetee'),
661 'falken' : PasswordHash('joshua') }
662
57817c2 edit
Markus authored
663
664 urls = ('/', 'hello',
665 '/logout/', 'logout',
666 '/register/', 'register')
87e5cba secure authentication
Josh Goldfoot authored
667
57817c2 edit
Markus authored
668 app = web.application(urls, globals())
669 render = web.template.render('templates/')
f126c6f session error
Davy authored
670
671 if web.config.get('_session') is None:
672 session = web.session.Session(app, web.session.DiskStore('sessions'),
57817c2 edit
Markus authored
673 initializer={'user': 'anonymous'})
f126c6f session error
Davy authored
674 web.config._session = session
675 else:
676 session = web.config._session
57817c2 edit
Markus authored
677
678 signin_form = form.Form(form.Textbox('username',
87e5cba secure authentication
Josh Goldfoot authored
679 form.Validator('Unknown username.',
57817c2 edit
Markus authored
680 lambda x: x in users.keys()),
681 description='Username:'),
682 form.Password('password',
683 description='Password:'),
684 validators = [form.Validator("Username and password didn't match.",
87e5cba secure authentication
Josh Goldfoot authored
685 lambda x: users[x.username].check_password(x.password)) ])
57817c2 edit
Markus authored
686
687 signup_form = form.Form(form.Textbox('username',
688 form.Validator('Username already exists.',
689 lambda x: x not in users.keys()),
690 description='Username:'),
691 form.Password('password',
692 description='Password:'),
693 form.Password('password_again',
694 description='Repeat your password:'),
695 validators = [form.Validator("Passwords didn't match.",
696 lambda i: i.password == i.password_again)])
697
698
699 class hello:
700 def GET(self):
701 my_signin = signin_form()
702 return render.hello(session.user, my_signin)
703
704 def POST(self):
705 my_signin = signin_form()
706 if not my_signin.validates():
707 return render.hello(session.user, my_signin)
708 else:
709 session.user = my_signin['username'].value
710 return render.hello(session.user, my_signin)
711
712
713 class logout:
714 def GET(self):
715 session.kill()
716 raise web.seeother('/')
717
718
719 class register:
720 def GET(self):
721 my_signup = signup_form()
722 return render.signup(my_signup)
87e5cba secure authentication
Josh Goldfoot authored
723
57817c2 edit
Markus authored
724 def POST(self):
725 my_signup = signup_form()
726 if not my_signup.validates():
727 return render.signup(my_signup)
728 else:
729 username = my_signup['username'].value
730 password = my_signup['password'].value
87e5cba secure authentication
Josh Goldfoot authored
731 users[username] = PasswordHash(password)
57817c2 edit
Markus authored
732 raise web.seeother('/')
733
734 if __name__ == "__main__":
735 app.run()
736
737 hello.html
738
739 $def with (user, form)
740 $if user == 'anonymous':
741 <p>You are not logged in.</p>
742 <p>
743 <form name="test" method="POST">
744 $:form.render()
745 <input type="submit" name="button" value="Login" />
746 </form>
747 </p>
748 <p><a href="./register/">Register</a></p>
749 $else:
750 <p>You are logged in as: $user</p>
751 <p><a href="./logout/">Logout</a></p>
752
753 signup.html
754
755 $def with (form)
756 <form name="test" method="POST">
757 $:form.render()
758 <input type="submit" value="Register" />
759 </form>
0872f0f edit
Markus authored
760
761 ## Deployment
762
6c39875 edit
Markus authored
763 Advantages / disadvantages of different solutions: App Engine, servers...
0872f0f edit
Markus authored
764
176e08a edit
anonymous authored
765
766
0872f0f edit
Markus authored
767 TODO: ...
Something went wrong with that request. Please try again.