Web2py8.5plus3minus4plus24plus2minus2 Reference Manual, 5th Edition Web2py Manual
web2py_manual_5th
web2py_manual_5th
web2py_manual_5th
User Manual:
Open the PDF directly: View PDF .
Page Count: 614
Download | |
Open PDF In Browser | View PDF |
MASSIMO DI PIERRO WEB2PY COMPLETE REFERENCE MANUAL, 5TH EDITION EXPERTS4SOLUTIONS Copyright 2008-2013 by Massimo Di Pierro. All rights reserved. THE CONTENT OF THIS BOOK IS PROVIDED UNDER THE TERMS OF THE CREATIVE COMMONS PUBLIC LICENSE BY-NC-ND 3.0. http://creativecommons.org/licenses/by-nc-nd/3.0/legalcode THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. Limit of Liability/Disclaimer of Warranty: While the publisher and author have used their best efforts in preparing this book, they make no representations or warranties with respect to the accuracy or completeness of the contents of this book and specifically disclaim any implied warranties of merchantability or fitness for a particular purpose. No warranty may be created ore extended by sales representatives or written sales materials. The advice and strategies contained herein may not be suitable for your situation. You should consult with a professional where appropriate. Neither the publisher nor author shall be liable for any loss of profit or any other commercial damages, including but not limited to special, incidental, consequential, or other damages. For more information about appropriate use of this material contact: Massimo Di Pierro School of Computing DePaul University 243 S Wabash Ave Chicago, IL 60604 (USA) Email: massimo.dipierro@gmail.com Library of Congress Cataloging-in-Publication Data: ISBN: 978-0-578-12021-8 Build Date: March 3, 2013 to my family Contents 1 Introduction 1.1 Principles . . . . . . . . 1.2 Web frameworks . . . 1.3 Model-View-Controller 1.4 Why web2py . . . . . . 1.5 Security . . . . . . . . . 1.6 In the box . . . . . . . 1.7 About this book . . . . 1.8 Support . . . . . . . . . 1.9 Contribute . . . . . . . 1.10 Elements of style . . . 1.11 License . . . . . . . . . 1.12 Acknowledgments . . 2 The Python language 2.1 About Python . . . 2.2 Starting up . . . . . 2.3 help, dir . . . . . . 2.4 Types . . . . . . . . 2.4.1 str . . . . . 2.4.2 list . . . . . 2.4.3 tuple . . . . 2.4.4 dict . . . . . 2.5 About indentation 2.6 for...in . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 25 26 28 31 33 36 37 39 40 40 41 42 . . . . . . . . . . 47 47 48 49 50 50 51 52 53 55 55 6 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . try...except...else...finally . . . . . . . def...return . . . . . . . . . . . . . . . . . 2.10.1 lambda . . . . . . . . . . . . . . . . class . . . . . . . . . . . . . . . . . . . . . Special attributes, methods and operators File input/output . . . . . . . . . . . . . . exec, eval . . . . . . . . . . . . . . . . . . . import . . . . . . . . . . . . . . . . . . . . . 2.15.1 os . . . . . . . . . . . . . . . . . . . 2.15.2 sys . . . . . . . . . . . . . . . . . . 2.15.3 datetime . . . . . . . . . . . . . . . 2.15.4 time . . . . . . . . . . . . . . . . . . 2.15.5 cPickle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 57 57 59 62 63 65 66 67 67 68 69 69 70 70 3 Overview 3.1 Startup . . . . . . . . . . . . . . . . . . . . 3.2 Simple examples . . . . . . . . . . . . . . 3.2.1 Say hello . . . . . . . . . . . . . . . 3.2.2 Debugging toolbar . . . . . . . . . 3.2.3 Let’s count . . . . . . . . . . . . . . 3.2.4 Say my name . . . . . . . . . . . . 3.2.5 Postbacks . . . . . . . . . . . . . . 3.2.6 Internationalization . . . . . . . . 3.3 An image blog . . . . . . . . . . . . . . . . 3.3.1 Adding authentication . . . . . . . 3.3.2 Adding grids . . . . . . . . . . . . 3.3.3 Configuring the layout . . . . . . . 3.4 A simple wiki . . . . . . . . . . . . . . . . 3.4.1 On date, datetime and time format 3.5 The built-in web2py wiki . . . . . . . . . 3.5.1 MARKMIN basics . . . . . . . . . 3.5.2 Oembed protocol . . . . . . . . . . 3.5.3 Referencing wiki content . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73 73 77 77 82 82 83 85 88 89 103 105 107 108 119 120 122 123 124 2.7 2.8 2.9 2.10 2.11 2.12 2.13 2.14 2.15 while if...elif...else 7 3.6 3.7 3.5.4 Wiki menus . . . . . . . . . . . . . . 3.5.5 Service functions . . . . . . . . . . . 3.5.6 Extending the auth.wiki feature . . 3.5.7 Components . . . . . . . . . . . . . . More on admin . . . . . . . . . . . . . . . . 3.6.1 Site . . . . . . . . . . . . . . . . . . . 3.6.2 About . . . . . . . . . . . . . . . . . 3.6.3 Design . . . . . . . . . . . . . . . . . 3.6.4 Errors . . . . . . . . . . . . . . . . . 3.6.5 Mercurial . . . . . . . . . . . . . . . 3.6.6 Application Wizard (experimental) 3.6.7 Configuring admin . . . . . . . . . . 3.6.8 Mobile admin . . . . . . . . . . . . . More on appadmin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 The core 4.1 Command line options . . . . . . . . . . . . . . . 4.2 Workflow . . . . . . . . . . . . . . . . . . . . . . . 4.3 Dispatching . . . . . . . . . . . . . . . . . . . . . 4.4 Libraries . . . . . . . . . . . . . . . . . . . . . . . 4.5 Applications . . . . . . . . . . . . . . . . . . . . . 4.6 API . . . . . . . . . . . . . . . . . . . . . . . . . . 4.6.1 Accessing the API from Python modules 4.7 request . . . . . . . . . . . . . . . . . . . . . . . . 4.8 response . . . . . . . . . . . . . . . . . . . . . . . . 4.9 session . . . . . . . . . . . . . . . . . . . . . . . . 4.9.1 Separate sessions . . . . . . . . . . . . . . 4.10 cache . . . . . . . . . . . . . . . . . . . . . . . . . 4.11 URL . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.11.1 Absolute urls . . . . . . . . . . . . . . . . 4.11.2 Digitally signed urls . . . . . . . . . . . . 4.12 HTTP and redirect . . . . . . . . . . . . . . . . . . 4.13 Internationalization, and Pluralization with T . . 4.13.1 Determining the language . . . . . . . . . 4.13.2 Translating variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125 125 126 127 128 128 131 131 135 138 139 140 142 142 . . . . . . . . . . . . . . . . . . . 145 145 149 152 155 160 162 163 165 170 174 177 177 180 182 182 184 185 186 188 8 4.14 4.15 4.16 4.17 4.18 4.19 4.20 4.21 4.22 4.13.3 Comments and multiple translations . . . . 4.13.4 Pluralization engine . . . . . . . . . . . . . . 4.13.5 Translations, pluralization, and MARKMIN Cookies . . . . . . . . . . . . . . . . . . . . . . . . . . Application init . . . . . . . . . . . . . . . . . . . . . URL rewrite . . . . . . . . . . . . . . . . . . . . . . . 4.16.1 Parameter-based system . . . . . . . . . . . . 4.16.2 Pattern-based system . . . . . . . . . . . . . 4.16.3 Routes on error . . . . . . . . . . . . . . . . . 4.16.4 Static asset management . . . . . . . . . . . . Running tasks in the background . . . . . . . . . . . 4.17.1 Cron . . . . . . . . . . . . . . . . . . . . . . . 4.17.2 Homemade task queues . . . . . . . . . . . . 4.17.3 Scheduler (experimental) . . . . . . . . . . . Third party modules . . . . . . . . . . . . . . . . . . Execution environment . . . . . . . . . . . . . . . . . Cooperation . . . . . . . . . . . . . . . . . . . . . . . Logging . . . . . . . . . . . . . . . . . . . . . . . . . . WSGI . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.22.1 External middleware . . . . . . . . . . . . . . 4.22.2 Internal middleware . . . . . . . . . . . . . . 4.22.3 Calling WSGI applications . . . . . . . . . . 5 The views 5.1 Basic syntax . . . . . . . . . . . . . . . 5.1.1 for...in . . . . . . . . . . . . . 5.1.2 while . . . . . . . . . . . . . . . 5.1.3 if...elif...else . . . . . . . . 5.1.4 try...except...else...finally 5.1.5 def...return . . . . . . . . . . . 5.2 HTML helpers . . . . . . . . . . . . . . 5.2.1 XML . . . . . . . . . . . . . . . . 5.2.2 Built-in helpers . . . . . . . . . 5.2.3 Custom helpers . . . . . . . . . 5.3 BEAUTIFY . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188 189 191 192 193 193 194 196 200 201 204 205 207 208 218 219 221 222 223 223 224 224 . . . . . . . . . . . 227 229 229 230 230 231 232 233 235 236 250 251 9 5.4 5.5 5.6 5.7 Server-side DOM and parsing . . . . . 5.4.1 elements . . . . . . . . . . . . . 5.4.2 components . . . . . . . . . . . . 5.4.3 parent and siblings . . . . . . . 5.4.4 Replacing elements . . . . . . . 5.4.5 flatten . . . . . . . . . . . . . 5.4.6 Parsing . . . . . . . . . . . . . . Page layout . . . . . . . . . . . . . . . . 5.5.1 Default page layout . . . . . . 5.5.2 Customizing the default layout 5.5.3 Mobile development . . . . . . Functions in views . . . . . . . . . . . Blocks in views . . . . . . . . . . . . . 6 The database abstraction layer 6.1 Dependencies . . . . . . . . . . . . 6.2 Connection strings . . . . . . . . . 6.2.1 Connection pooling . . . . 6.2.2 Connection failures . . . . . 6.2.3 Replicated databases . . . . 6.3 Reserved keywords . . . . . . . . . 6.4 DAL, Table, Field . . . . . . . . . . . 6.5 Record representation . . . . . . . 6.6 Migrations . . . . . . . . . . . . . . 6.7 Fixing broken migrations . . . . . 6.8 insert . . . . . . . . . . . . . . . . . 6.9 commit and rollback . . . . . . . . . 6.10 Raw SQL . . . . . . . . . . . . . . . 6.10.1 Timing queries . . . . . . . 6.10.2 executesql . . . . . . . . . . 6.10.3 _lastsql . . . . . . . . . . . 6.11 drop . . . . . . . . . . . . . . . . . . 6.12 Indexes . . . . . . . . . . . . . . . . 6.13 Legacy databases and keyed tables 6.14 Distributed transaction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252 252 254 254 254 255 255 255 259 262 263 264 265 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 267 267 269 271 272 272 272 273 275 281 282 283 284 285 285 285 286 287 287 287 288 10 6.15 More on uploads . . . . . . . . . . . . . . . . . . . . . 6.16 Query, Set, Rows . . . . . . . . . . . . . . . . . . . . . . . 6.17 select . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.17.1 Shortcuts . . . . . . . . . . . . . . . . . . . . . . 6.17.2 Fetching a Row . . . . . . . . . . . . . . . . . . . 6.17.3 Recursive selects . . . . . . . . . . . . . . . . . 6.17.4 Serializing Rows in views . . . . . . . . . . . . . 6.17.5 orderby, groupby, limitby, distinct, having . . . 6.17.6 Logical operators . . . . . . . . . . . . . . . . . 6.17.7 count, isempty, delete, update . . . . . . . . . . 6.17.8 Expressions . . . . . . . . . . . . . . . . . . . . 6.17.9 case . . . . . . . . . . . . . . . . . . . . . . . . 6.17.10 update_record . . . . . . . . . . . . . . . . . . . 6.17.11 Inserting and updating from a dictionary . . . 6.17.12 first and last . . . . . . . . . . . . . . . . . . . 6.17.13 as_dict and as_list . . . . . . . . . . . . . . . . 6.17.14 Combining rows . . . . . . . . . . . . . . . . . 6.17.15 find, exclude, sort . . . . . . . . . . . . . . . . . 6.18 Other methods . . . . . . . . . . . . . . . . . . . . . . 6.18.1 update_or_insert . . . . . . . . . . . . . . . . . 6.18.2 validate_and_insert, validate_and_update . . . 6.18.3 smart_query (experimental) . . . . . . . . . . . 6.19 Computed fields . . . . . . . . . . . . . . . . . . . . . 6.20 Virtual fields . . . . . . . . . . . . . . . . . . . . . . . . 6.20.1 New style virtual fields . . . . . . . . . . . . . 6.20.2 Old style virtual fields . . . . . . . . . . . . . . 6.21 One to many relation . . . . . . . . . . . . . . . . . . . 6.21.1 Inner joins . . . . . . . . . . . . . . . . . . . . . 6.21.2 Left outer join . . . . . . . . . . . . . . . . . . . 6.21.3 Grouping and counting . . . . . . . . . . . . . 6.22 Many to many . . . . . . . . . . . . . . . . . . . . . . . 6.23 list:, and contains . . . . . . . . . . . . . . . . 6.24 Other operators . . . . . . . . . . . . . . . . . . . . . . 6.24.1 like, regexp, startswith, contains, upper, lower 6.24.2 year, month, day, hour, minutes, seconds . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 289 290 291 293 293 294 295 297 299 299 300 300 301 301 302 302 302 303 304 304 305 305 306 307 307 308 310 311 313 313 314 315 317 317 318 11 6.25 6.26 6.27 6.28 6.29 6.24.3 belongs . . . . . . . . . . . . . . . . . . . . . . . . 6.24.4 sum, avg, min, max and len . . . . . . . . . . . . . . 6.24.5 Substrings . . . . . . . . . . . . . . . . . . . . . . 6.24.6 Default values with coalesce and coalesce_zero Generating raw sql . . . . . . . . . . . . . . . . . . . . . Exporting and importing data . . . . . . . . . . . . . . . 6.26.1 CSV (one Table at a time) . . . . . . . . . . . . . 6.26.2 CSV (all tables at once) . . . . . . . . . . . . . . 6.26.3 CSV and remote database synchronization . . . 6.26.4 HTML and XML (one Table at a time) . . . . . . 6.26.5 Data representation . . . . . . . . . . . . . . . . Caching selects . . . . . . . . . . . . . . . . . . . . . . . Self-Reference and aliases . . . . . . . . . . . . . . . . . Advanced features . . . . . . . . . . . . . . . . . . . . . 6.29.1 Table inheritance . . . . . . . . . . . . . . . . . . 6.29.2 filter_in and filter_out . . . . . . . . . . . . . 6.29.3 before and after callbacks . . . . . . . . . . . . . 6.29.4 Record versioning . . . . . . . . . . . . . . . . . 6.29.5 Common fields and multi-tenancy . . . . . . . . 6.29.6 Common filters . . . . . . . . . . . . . . . . . . . 6.29.7 Custom Field types (experimental) . . . . . . . 6.29.8 Using DAL without define tables . . . . . . . . 6.29.9 PostGIS, SpatiaLite, and MS Geo (experimental) 6.29.10 Copy data from one db into another . . . . . . . 6.29.11 Note on new DAL and adapters . . . . . . . . . 6.29.12 Gotchas . . . . . . . . . . . . . . . . . . . . . . . 7 Forms and validators 7.1 FORM . . . . . . . . . . . . . . . . . . . . . . 7.1.1 The process and validate methods 7.1.2 Hidden fields . . . . . . . . . . . . 7.1.3 keepvalues . . . . . . . . . . . . . . 7.1.4 onvalidation . . . . . . . . . . . . . 7.1.5 Detect record change . . . . . . . . 7.1.6 Forms and redirection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 319 319 320 320 321 322 322 322 323 325 326 327 328 329 329 330 330 332 333 334 334 335 336 337 338 341 . . . . . . . 345 346 349 350 352 352 353 353 12 Multiple forms per page . . . . . . . . . Sharing forms . . . . . . . . . . . . . . . Adding buttons to FORMs . . . . . . . More about manipulation of FORMs . SQLFORM . . . . . . . . . . . . . . . . . . . . . . . 7.2.1 SQLFORM and insert/update/delete . . . 7.2.2 SQLFORM in HTML . . . . . . . . . . . . . 7.2.3 SQLFORM and uploads . . . . . . . . . . . 7.2.4 Storing the original filename . . . . . . 7.2.5 autodelete . . . . . . . . . . . . . . . . . 7.2.6 Links to referencing records . . . . . . . 7.2.7 Pre-populating the form . . . . . . . . . 7.2.8 Adding extra form elements to SQLFORM 7.2.9 SQLFORM without database IO . . . . . . Other types of Forms . . . . . . . . . . . . . . . 7.3.1 SQLFORM.factory . . . . . . . . . . . . . . 7.3.2 One form for multiple tables . . . . . . 7.3.3 Confirmation Forms . . . . . . . . . . . 7.3.4 Form to edit a dictionary . . . . . . . . CRUD . . . . . . . . . . . . . . . . . . . . . . . . 7.4.1 Settings . . . . . . . . . . . . . . . . . . 7.4.2 Messages . . . . . . . . . . . . . . . . . . 7.4.3 Methods . . . . . . . . . . . . . . . . . . 7.4.4 Record versioning . . . . . . . . . . . . Custom forms . . . . . . . . . . . . . . . . . . . 7.5.1 CSS conventions . . . . . . . . . . . . . 7.5.2 Hide errors . . . . . . . . . . . . . . . . Validators . . . . . . . . . . . . . . . . . . . . . 7.6.1 Validators . . . . . . . . . . . . . . . . . 7.6.2 Database validators . . . . . . . . . . . 7.6.3 Custom validators . . . . . . . . . . . . 7.6.4 Validators with dependencies . . . . . . Widgets . . . . . . . . . . . . . . . . . . . . . . . 7.7.1 Autocomplete widget . . . . . . . . . . SQLFORM.grid and SQLFORM.smartgrid . . . . . . . 7.1.7 7.1.8 7.1.9 7.1.10 7.2 7.3 7.4 7.5 7.6 7.7 7.8 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 354 355 356 356 356 361 363 364 366 367 367 369 370 370 371 371 372 373 373 374 375 377 378 380 382 383 384 385 386 397 399 401 401 403 404 13 8 Emails and SMS 8.1 Setting up email . . . . . . . . . . . . . . . . . . . . . 8.1.1 Configuring email for Google App Engine . 8.1.2 x509 and PGP Encryption . . . . . . . . . . . 8.2 Sending emails . . . . . . . . . . . . . . . . . . . . . 8.2.1 Simple text email . . . . . . . . . . . . . . . . 8.2.2 HTML emails . . . . . . . . . . . . . . . . . . 8.2.3 Combining text and HTML emails . . . . . . 8.2.4 cc and bcc emails . . . . . . . . . . . . . . . . 8.2.5 Attachments . . . . . . . . . . . . . . . . . . . 8.2.6 Multiple attachments . . . . . . . . . . . . . . 8.3 Sending SMS messages . . . . . . . . . . . . . . . . . 8.4 Using the template system to generate messages . . 8.5 Sending messages using a background task . . . . . 8.6 Reading and managing email boxes (Experimental) 8.6.1 Connection . . . . . . . . . . . . . . . . . . . 8.6.2 Fetching mail and updating flags . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 413 413 414 414 415 415 415 416 416 416 416 416 417 418 419 420 421 9 Access Control 9.1 Authentication . . . . . . . . . . . . . . . . . . . . 9.1.1 Restrictions on registration . . . . . . . . 9.1.2 Integration with OpenID, Facebook, etc. 9.1.3 CAPTCHA and reCAPTCHA . . . . . . . 9.1.4 Customizing Auth . . . . . . . . . . . . . . 9.1.5 Renaming Auth tables . . . . . . . . . . . 9.1.6 Other login methods and login forms . . 9.1.7 Record versioning . . . . . . . . . . . . . 9.1.8 Mail and Auth . . . . . . . . . . . . . . . . 9.2 Authorization . . . . . . . . . . . . . . . . . . . . 9.2.1 Decorators . . . . . . . . . . . . . . . . . . 9.2.2 Combining requirements . . . . . . . . . 9.2.3 Authorization and CRUD . . . . . . . . . 9.2.4 Authorization and downloads . . . . . . 9.2.5 Access Control and Basic Authentication 9.2.6 Manual Authentication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 425 428 431 432 434 435 437 438 444 445 446 448 449 450 451 451 452 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 9.3 9.2.7 Settings and messages . . . . . . . . . . . . . . . . . . 452 Central Authentication Service . . . . . . . . . . . . . . . . . 458 9.3.1 Using web2py to authorize non-web2py apps . . . . 460 10 Services 10.1 Rendering a dictionary . . . . . . . . 10.1.1 HTML, XML, and JSON . . . 10.1.2 Generic views . . . . . . . . . 10.1.3 Rendering Rows . . . . . . . . 10.1.4 Custom formats . . . . . . . . 10.1.5 RSS . . . . . . . . . . . . . . . 10.1.6 CSV . . . . . . . . . . . . . . . 10.2 Remote procedure calls . . . . . . . 10.2.1 XMLRPC . . . . . . . . . . . . 10.2.2 JSONRPC . . . . . . . . . . . 10.2.3 JSONRPC and Pyjamas . . . 10.2.4 AMFRPC . . . . . . . . . . . 10.2.5 SOAP . . . . . . . . . . . . . . 10.3 Low level API and other recipes . . 10.3.1 simplejson . . . . . . . . . . . 10.3.2 PyRTF . . . . . . . . . . . . . 10.3.3 ReportLab and PDF . . . . . 10.4 Restful Web Services . . . . . . . . . 10.4.1 parse_as_rest (experimental) 10.4.2 smart_query (experimental) . 10.4.3 Access Control . . . . . . . . 10.5 Services and Authentication . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 463 463 464 464 466 467 467 469 470 472 473 474 477 480 480 480 481 482 483 486 491 491 492 11 jQuery and Ajax 11.1 web2py_ajax.html . . . . . . . . . . 11.2 jQuery effects . . . . . . . . . . . . 11.2.1 Conditional fields in forms 11.2.2 Confirmation on delete . . 11.3 The ajax function . . . . . . . . . . 11.3.1 Eval target . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 495 495 500 504 506 507 508 . . . . . . 15 11.3.2 Auto-completion . . . . . . . . . . . . . . . . . . . . . 508 11.3.3 Ajax form submission . . . . . . . . . . . . . . . . . . 511 11.3.4 Voting and rating . . . . . . . . . . . . . . . . . . . . . 512 12 Components and plugins 12.1 Components . . . . . . . . . . . . . . . . . . . . . . 12.1.1 Client-Server component communications 12.1.2 Trapped Ajax links . . . . . . . . . . . . . . 12.2 Plugins . . . . . . . . . . . . . . . . . . . . . . . . . 12.2.1 Component plugins . . . . . . . . . . . . . 12.2.2 Plugin manager . . . . . . . . . . . . . . . . 12.2.3 Layout plugins . . . . . . . . . . . . . . . . 12.2.4 Plugins repositories . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 515 515 520 521 522 526 527 528 529 13 Deployment recipes 13.1 anyserver.py . . . . . . . . . . . . . . . . . . . . . . . . . 13.2 Linux and Unix . . . . . . . . . . . . . . . . . . . . . . . 13.2.1 One step production deployment . . . . . . . . 13.2.2 Apache setup . . . . . . . . . . . . . . . . . . . . 13.2.3 mod_wsgi . . . . . . . . . . . . . . . . . . . . . . 13.2.4 Setting password . . . . . . . . . . . . . . . . . . 13.2.5 mod_wsgi and SSL . . . . . . . . . . . . . . . . . 13.2.6 mod_proxy . . . . . . . . . . . . . . . . . . . . . 13.2.7 Start as Linux daemon . . . . . . . . . . . . . . . 13.2.8 Nginx . . . . . . . . . . . . . . . . . . . . . . . . 13.2.9 Lighttpd . . . . . . . . . . . . . . . . . . . . . . . 13.2.10 Shared hosting with mod_python . . . . . . . . 13.2.11 Cherokee with FastCGI . . . . . . . . . . . . . . 13.2.12 Postgresql . . . . . . . . . . . . . . . . . . . . . . 13.2.13 Start the scheduler as a Linux service (upstart) 13.3 Windows . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.3.1 Apache and mod_wsgi . . . . . . . . . . . . . . 13.3.2 Start as Windows service . . . . . . . . . . . . . 13.3.3 Start the scheduler as a Windows service . . . . 13.4 Securing sessions and admin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 531 534 535 535 535 536 539 539 540 542 543 546 548 548 550 551 552 552 555 556 556 . . . . . . . . . . . . . . . . 16 13.5 Efficiency and scalability . . . . . . . . . . . . . . . 13.5.1 Efficiency tricks . . . . . . . . . . . . . . . . 13.5.2 Sessions in database . . . . . . . . . . . . . 13.5.3 HAProxy a high availability load balancer 13.5.4 Cleaning up sessions . . . . . . . . . . . . . 13.5.5 Uploading files in database . . . . . . . . . 13.5.6 Collecting tickets . . . . . . . . . . . . . . . 13.5.7 Memcache . . . . . . . . . . . . . . . . . . . 13.5.8 Sessions in memcache . . . . . . . . . . . . 13.5.9 Caching with Redis . . . . . . . . . . . . . 13.5.10 Sessions in Redis . . . . . . . . . . . . . . . 13.5.11 Removing applications . . . . . . . . . . . . 13.5.12 Using replicated databases . . . . . . . . . 13.5.13 Compress static files . . . . . . . . . . . . . 13.6 Deploying on PythonAnywhere . . . . . . . . . . 13.7 Deploying on Heroku . . . . . . . . . . . . . . . . 13.8 Deploying on EC2 . . . . . . . . . . . . . . . . . . . 13.9 Deploying on Google App Engine . . . . . . . . . 13.9.1 Configuration . . . . . . . . . . . . . . . . . 13.9.2 Running and deployment . . . . . . . . . . 13.9.3 Configuring the handler . . . . . . . . . . . 13.9.4 Avoid the filesystem . . . . . . . . . . . . . 13.9.5 Memcache . . . . . . . . . . . . . . . . . . . 13.9.6 Datastore issues . . . . . . . . . . . . . . . . 13.9.7 GAE and https . . . . . . . . . . . . . . . . 14 Other recipes 14.1 Upgrading . . . . . . . . . . . . . . . . . . . . . . 14.2 How to distribute your applications as binaries 14.3 WingIDE, Rad2Py, and Eclipse . . . . . . . . . . 14.4 SQLDesigner . . . . . . . . . . . . . . . . . . . . . 14.5 Publishing a folder . . . . . . . . . . . . . . . . . 14.6 Functional testing . . . . . . . . . . . . . . . . . . 14.7 Building a minimalist web2py . . . . . . . . . . . 14.8 Fetching an external URL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 557 559 559 560 561 562 563 564 565 565 565 566 566 567 568 570 572 573 574 575 577 577 578 578 579 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 581 581 581 582 583 584 585 587 588 17 14.9 Pretty dates . . . . . . . . . . . . . . . . 14.10Geocoding . . . . . . . . . . . . . . . . . 14.11Pagination . . . . . . . . . . . . . . . . . 14.12httpserver.log and the Log File Format 14.13Populating database with dummy data 14.14Accepting credit card payments . . . . . 14.14.1 Google Wallet . . . . . . . . . . . 14.14.2 Paypal . . . . . . . . . . . . . . . 14.14.3 Stripe.com . . . . . . . . . . . . . 14.14.4 Authorize.Net . . . . . . . . . . . 14.15Dropbox API . . . . . . . . . . . . . . . . 14.16Twitter API . . . . . . . . . . . . . . . . . 14.17Streaming virtual files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 588 589 589 590 591 592 593 594 594 596 597 598 599 Index 603 Bibliography 609 CONTENTS 19 Preface I believe that the ability to easily build high quality web applications is of critical importance for the growth of a free and open society. This prevents the biggest players from monopolizing the flow of information. Hence I started the web2py project in 2007, primarily as a teaching tool with the goal of making web development easier, faster, and more secure. Over time, it has managed to win the affection of thousands of knowledgeable users and hundreds of developers. Our collective effort has created one of the most full-featured Open Source Web Frameworks for enterprise web development. As a result, in 2011, web2py won the Bossie Award for best Open Source Development Software, and in 2012 it won the Technology of the Year award from InfoWorld. As you will learn in the following pages, web2py tries to lower the barrier of entry to web development by focusing on three main goals: Ease of use. This means reducing the learning and deployment time as well as development and maintenance costs. This is why web2py is a full-stack framework without dependencies. It requires no installation and has no configuration files. Everything works out of the box, including a web server, database and a web-based IDE that gives access to all the main features. The API includes just 12 core objects, which are easy to work with and memorize. It can interoperate with most web servers, databases and all Python libraries. Rapid development. Every function of web2py has a default behavior (which 22 web2py complete reference manual, 5th edition can be overridden). For example, as soon as you have specified your data models, you will have access to a web-based database administration panel. Also, web2py automatically generates forms for your data and it allows you to easily expose the data in HTML, XML, JSON, RSS, etc. web2py provides some high level widgets such as the wiki and the grid to rapidly build complex applications. Security. The web2py Database Abstraction Layer (DAL) eliminates SQL Injections. The template language prevents Cross Site Scripting vulnerabilities. The forms generated by web2py provide field validation and block Cross Site Request Forgeries. Passwords are always stored hashed. Sessions are stored server-side by default to prevent Cookie Tampering. Session cookies are UUID to prevent Session Hijacking. is built from the user perspective and is constantly being optimized internally to become faster and leaner, whilst always maintaining backwards compatibility. web2py web2py is free for you to use. If you benefit from it, I hope you will feel encouraged to pay it forward by contributing back to society in whatever form you choose. 1 Introduction web2py [1] is a free, open-source web framework for agile development of secure database-driven web applications; it is written in Python [2] and programmable in Python. web2py is a full-stack framework, meaning that it contains all the components you need to build fully functional web applications. web2py is designed to guide a web developer to follow good software engineering practices, such as using the Model View Controller (MVC) pattern. web2py separates the data representation (the model) from the data presentation (the view) and also from the application logic and workflow (the controller). web2py provides libraries to help the developer design, implement, and test each of these three parts separately, and makes them work together. web2py is built for security. This means that it automatically addresses many of the issues that can lead to security vulnerabilities, by following well established practices. For example, it validates all input (to prevent injections), escapes all output (to prevent cross-site scripting), renames uploaded files (to prevent directory traversal attacks). web2py takes care of main security issues, so developers have less chances of introducing vulnerabilities. web2py includes a Database Abstraction Layer (DAL) that writes SQL [3] dynamically so that you, the developer, don’t have to. The DAL knows how to generate SQL transparently for SQLite [4], MySQL [5], PostgreSQL [6], MSSQL [7], FireBird [8], Oracle [9], IBM DB2 [10], Informix [11], Ingres [12], and MongoDB [13]. The DAL can also generate function calls for the Google Datastore when running on the 24 web2py complete reference manual, 5th edition Google App Engine (GAE) [14]. Experimentally we support more databases and new ones are constantly added. Please check on the web2py web site and mailing list for more recent adapters. Once one or more database tables are defined, web2py automatically generates a fully functional webbased database administration interface to access the database and the tables. web2py differs from other web frameworks in that it is the only framework to fully embrace the Web 2.0 paradigm, where the web is the computer. In fact, web2py does not require installation or configuration; it runs on any architecture that can run Python (Windows, Windows CE, Mac OS X, iOS, and Unix/Linux), and the development, deployment, and maintenance phases for the applications can be done via a local or remote web interface. web2py runs with CPython (the C implementation) and PyPy (Python written in Python), on Python versions 2.5, 2.6, and 2.7. web2py provides a ticketing system for error events. If an error occurs, a ticket is issued to the user, and the error is logged for the administrator. web2py is open source and released under the LGPL version 3 license. Another important feature of web2py is that we, its developers, commit to maintain backward compatibility in future versions. We have done so since the first release of web2py in October, 2007. New features have been added and bugs have been fixed, but if a program worked with web2py 1.0, that program will work even better today. Here are some examples of web2py statements that illustrate its power and simplicity. The following code: 1 db.define_table('person', Field('name'), Field('image', 'upload')) creates a database table called "person" with two fields: "name", a string; and "image", something that needs to be uploaded (the actual image). If the table already exists but does not match this definition, it is altered appropriately. Given the table defined above, the following code: 1 form = SQLFORM(db.person).process() creates an insert form for this table that allows users to upload images. It also validates the submitted form, renames the uploaded image in a secure way, stores the image in a file, inserts the corresponding record in the database, prevents double submission, and eventually modifies the form introduction 25 itself by adding error messages if the data submitted by the user does not pass validation. This code embeds a fully working wiki with tags, search, tag cloud, permissions, media attachments, and oembed support: 1 def index(): return auth.wiki() The following code instead: 1 2 @auth.requires_permission('read','person') def f(): .... prevents visitors from accessing the function f unless the visitor is a member of a group whose members have permissions to "read" records of table "person". If the visitor is not logged in, the visitor gets directed to a login page (provided by default by web2py). web2py also supports components, i.e. actions which can be loaded in a view and interact with the visitor via Ajax without re-loading the entire page. This is done via a LOAD helper which allows very modular design of applications; it is discussed in chapter 3 in the context of the wiki and, in some detail, in the last chapter of this book. This 5th edition of the book describes web2py 2.4.1 and later versions. 1.1 Principles Python programming typically follows these basic principles: • Don’t repeat yourself (DRY). • There should be only one way of doing things. • Explicit is better than implicit. web2py fully embraces the first two principles by forcing the developer to use sound software engineering practices that discourage repetition of code. web2py guides the developer through almost all the tasks common in web application development (creating and processing forms, managing sessions, cookies, errors, etc.). web2py differs from other frameworks with regard to the third principle, which sometimes conflicts with the other two. In particular, web2py does 26 web2py complete reference manual, 5th edition not import user applications, but executes them in a predefined context. This context exposes the Python keywords, as well as the web2py keywords. To some this may appear as magic, but it should not. Simply, in practice, some modules are already imported without you doing so. web2py is trying to avoid the annoying characteristic of other frameworks that force the developer to import the same modules at the top of every model and controller. web2py, by importing its own modules, saves time and prevents mistakes, thus following the spirit of "don’t repeat yourself" and "there should be only one way of doing things". If the developer wishes to use other Python modules or third-party modules, those modules must be imported explicitly, as in any other Python program. 1.2 Web frameworks At its most fundamental level, a web application consists of a set of programs (or functions) that are executed when the corresponding URL is visited. The output of the program is returned to the visitor and rendered by the browser. The purpose of web frameworks is to allow developers to build new apps quickly, easily and without mistakes. This is done by providing APIs and tools that reduce and simplify the amount of coding that is required. The two classic approaches for developing web applications are: • Generating HTML [15] [16] programmatically. • Embedding code into HTML pages. The first model is the one that was followed, for example, by early CGI scripts. The second model is followed, for example, by PHP [17] (where the code is in PHP, a C-like language), ASP (where the code is in Visual Basic), and JSP (where the code is in Java). Here is an example of a PHP program that, when executed, retrieves data from a database and returns an HTML page showing the selected records: 1 2 Records
mysql_connect(localhost,username,password); introduction 3 4 5 6 7 8 9 10 11 12 13 14 27 @mysql_select_db(database) or die( "Unable to select database"); $query="SELECT * FROM contacts"; $result=mysql_query($query); mysql_close(); $i=0; while ($i < mysql_numrows($result)) { $name=mysql_result($result,$i,"name"); $phone=mysql_result($result,$i,"phone"); echo "$name
Phone:$phone
"; $i++; } ?> The problem with this approach is that code is embedded into HTML, but the very same code also needs to generate additional HTML and to generate SQL statements to query the database, entangling multiple layers of the application and making it difficult to read and maintain. The situation is even worse for Ajax applications, and the complexity grows with the number of pages (files) that make up the application. The functionality of the above example can be expressed in web2py with two lines of Python code: 1 2 def index(): return HTML(BODY(H1('Records'), db().select(db.contacts.ALL))) In this simple example, the HTML page structure is represented programmatically by the HTML, BODY, and H1 objects; the database db is queried by the select command; finally, everything is serialized into HTML. Notice that db is not a keyword but a user defined variable. We will use this name consistently to refer to a database connection to avoid confusion. Web frameworks are typically categorized as one of two types: A "glued" framework is built by assembling (gluing together) several third-party components. A "full-stack" framework is built by creating components designed specifically to be tightly integrated and work together. web2py is a full-stack framework. Almost all of its components are built from scratch and are designed to work together, but they function just as well outside of the complete web2py framework. For example, the Database Abstraction Layer (DAL) or the template language can be used independently of the web2py framework by importing gluon.dal or gluon.template into your own Python applications. gluon is the name of the web2py module that contains system 28 web2py complete reference manual, 5th edition libraries. Some web2py libraries, such as building and processing forms from database tables, have dependencies on other portions of web2py. web2py can also work with third-party Python libraries, including other template languages and DALs, but they will not be as tightly integrated as the original components. 1.3 Model-View-Controller web2py encourages the developer to separate data representation (the model), data presentation (the view) and the application workflow (the controller). Let’s consider again the previous example and see how to build a web2py application around it. Here is an example of the web2py MVC edit interface: The typical workflow of a request in web2py is described in the following diagram: introduction 29 In the diagram: • The Server can be the web2py built-in web server or a third-party server, such as Apache. The Server handles multi-threading. • "main" is the main WSGI application. It performs all common tasks and wraps user applications. It deals with cookies, sessions, transactions, URL routing and reverse routing, and dispatching. It can serve and stream static files if the web server is not doing it already. • The Models, Views and Controller components make up the user application. • Multiple applications can be hosted in the same web2py instance. • The dashed arrows represent communication with the database engine(s). The database queries can be written in raw SQL (discouraged) or by using the web2py Database Abstraction Layer (recommended), so that web2py application code is not dependent on the specific database engine. • The dispatcher maps the requested URL to a function call in the controller. The output of the function can be a string or a dictionary of symbols (a hash table). The data in the dictionary is rendered by a view. If the visitor requests an HTML page (the default), the dictionary is rendered into an HTML page. If the visitor requests the same page in XML, 30 web2py complete reference manual, 5th edition web2py tries to find a view that can render the dictionary in XML. The developer can create views to render pages in any of the already supported protocols (HTML, XML, JSON, RSS, CSV, and RTF) or in additional custom protocols. • All calls are wrapped into a transaction, and any uncaught exception causes the transaction to be rolled back. If the request succeeds, the transaction is committed. • web2py also handles sessions and session cookies automatically, and when a transaction is committed, the session is also stored, unless specified otherwise. • It is possible to register recurrent tasks (via cron) to run at scheduled times and/or after the completion of certain actions. In this way it is possible to run long and compute-intensive tasks in the background without slowing down navigation. Here is a minimal and complete MVC application, consisting of three files: "db.py" is the model: 1 2 3 4 db = DAL('sqlite://storage.sqlite') db.define_table('contact', Field('name'), Field('phone')) It connects to the database (in this example a SQLite database stored in the storage.sqlite file) and defines a table called contact. If the table does not exist, web2py creates it and, transparently and in the background, generates SQL code in the appropriate SQL dialect for the specific database engine used. The developer can see the generated SQL but does not need to change the code if the database back-end, which defaults to SQLite, is replaced with MySQL, PostgreSQL, MSSQL, FireBird, Oracle, DB2, Informix, Interbase, Ingres, and the Google App Engine (both SQL and NoSQL). Once a table is defined and created, web2py also generates a fully functional web-based database administration interface, called appadmin, to access the database and the tables. "default.py" is the controller: 1 def contacts(): introduction 31 grid=SQLFORM.grid(db.contact, user_signature=False) return locals() 2 3 In web2py, URLs are mapped to Python modules and function calls. In this case, the controller contains a single function (or "action") called contacts. An action may return a string (the returned web page) or a Python dictionary (a set of key:value pairs) or the set of local variables (as in this example). If the function returns a dictionary, it is passed to a view with the same name as the controller/function, which in turn renders the page. In this example, the function contacts generates a select/search/create/update/delete grid for table db.contact and returns the grid to the view. "default/contacts.html" is the view: 1 2 3 {{extend 'layout.html'}}Manage My Contacts
{{=grid}} This view is called automatically by web2py after the associated controller function (action) is executed. The purpose of this view is to render the variables in the returned dictionary (in our case grid) into HTML. The view file is written in HTML, but it embeds Python code delimited by the special {{ and }} delimiters. This is quite different from the PHP code example, because the only code embedded into the HTML is "presentation layer" code. The "layout.html" file referenced at the top of the view is provided by web2py and constitutes the basic layout for all web2py applications. The layout file can easily be modified or replaced. 1.4 Why web2py web2py is one of many web application frameworks, but it has compelling and unique features. web2py was originally developed as a teaching tool, with the following primary motivations: • Easy for users to learn server-side web development without compromising functionality. For this reason, web2py requires no installation and no configuration, has no dependencies (except for the source code distribution, which requires Python 2.5 and its standard library modules), and exposes most of its functionality via a Web interface, 32 web2py complete reference manual, 5th edition including an Integrated Development Environment with Debugger and database interface. • web2py has been stable from day one because it follows a top-down design; i.e., its API was designed before it was implemented. Even as new functionality has been added, web2py has never broken backwards compatibility, and it will not break compatibility when additional functionality is added in the future. • web2py proactively addresses the most important security issues which plague many modern web applications, as determined by OWASP [18] below. • web2py is lightweight. Its core libraries, including the Database Abstraction Layer, the template language, and all the helpers amount to 1.4MB. The entire source code including sample applications and images amounts to 10.4MB. • web2py has a small footprint and is very fast. It uses the Rocket [19] WSGI web server developed by Timothy Farrell. It is as fast as Apache with mod_wsgi, and supports SSL and IPv6. • web2py uses Python syntax for models, controllers, and views, but does not import models and controllers (as all the other Python frameworks do) - instead it executes them. This means that apps can be installed, uninstalled, and modified without having to restart the web server (even in production), and different apps can coexist without their modules interfering with one another. • web2py uses a Database Abstraction Layer (DAL) instead of an Object Relational Mapper (ORM). From a conceptual point of view, this means that different database tables are mapped into different instances of one Table class and not into different classes, while records are mapped into instances of one Row class, not into instances of the corresponding table class. From a practical point of view, it means that SQL syntax maps almost one-to-one into DAL syntax, and there is no complex metaclass programming going on under the hood as in popular ORMs, which would add latency. introduction 33 WSGI [20] [21] (Web Server Gateway Interface) is an emerging Python standard for communication between a web server and Python applications). Here is a screenshot of the main web2py admin interface: 1.5 Security The Open Web Application Security Project [18] (OWASP) is a free and open worldwide community focused on improving the security of application software. OWASP has listed the top ten security issues that put web applications at risk. That list is reproduced here, along with a description of how each issue is addressed by web2py: • "Cross Site Scripting (XSS): XSS flaws occur whenever an application takes user supplied data and sends it to a web browser without first validating or encoding that content. XSS allows attackers to execute scripts in the victim’s browser which can hijack user sessions, deface web sites, possibly introduce worms, etc." web2py, by default, escapes all variables rendered in the view, preventing XSS. • "Injection Flaws: Injection flaws, particularly SQL injection, are common in web applications. Injection occurs when user-supplied data is sent to an interpreter as part of a command or query. The attacker’s hostile data tricks the interpreter into executing unintended commands or changing data." web2py includes a Database Abstraction Layer that makes SQL injection impossible. Normally, SQL statements are not written by the developer. Instead, 34 web2py complete reference manual, 5th edition SQL is generated dynamically by the DAL, ensuring that all inserted data is properly escaped. • "Malicious File Execution: Code vulnerable to remote file inclusion (RFI) allows attackers to include hostile code and data, resulting in devastating attacks, such as total server compromise." web2py allows only exposed functions to be executed, preventing malicious file execution. Imported functions are never exposed; only actions are exposed. web2py uses a Web-based administration interface which makes it very easy to keep track of what is exposed and what is not. • "Insecure Direct Object Reference: A direct object reference occurs when a developer exposes a reference to an internal implementation object, such as a file, directory, database record, or key, as a URL or form parameter. Attackers can manipulate those references to access other objects without authorization." web2py does not expose any internal objects; moreover, web2py validates all URLs, thus preventing directory traversal attacks. web2py also provides a simple mechanism to create forms that automatically validate all input values. • "Cross Site Request Forgery (CSRF): A CSRF attack forces a logged-on victim’s browser to send a pre-authenticated request to a vulnerable web application, which then forces the victim’s browser to perform a hostile action to the benefit of the attacker. CSRF can be as powerful as the web application that it attacks." web2py prevents CSRF as well as accidental double submission of forms by assigning a one-time random token to each form. Moreover web2py uses UUID for session cookie. • "Information Leakage and Improper Error Handling: Applications can unintentionally leak information about their configuration, internal workings, or violate privacy through a variety of application problems. Attackers use this weakness to steal sensitive data, or conduct more serious attacks." web2py includes a ticketing system. No error can result in code being exposed to the users. All errors are logged and a ticket is issued to the user that allows error tracking. But errors and source code are accessible only to the administrator. • "Broken Authentication and Session Management: Account credentials introduction 35 and session tokens are often not properly protected. Attackers compromise passwords, keys, or authentication tokens to assume other users’ identities." web2py provides a built-in mechanism for administrator authentication, and it manages sessions independently for each application. The administrative interface also forces the use of secure session cookies when the client is not "localhost". For applications, it includes a powerful Role Based Access Control API. • "Insecure Cryptographic Storage: Web applications rarely use cryptographic functions properly to protect data and credentials. Attackers use weakly protected data to conduct identity theft and other crimes, such as credit card fraud." web2py uses the MD5 or the HMAC+SHA512 hash algorithms to protect stored passwords. Other algorithms are also available. • "Insecure Communications: Applications frequently fail to encrypt network traffic when it is necessary to protect sensitive communications." web2py includes the SSL-enabled [22] Rocket WSGI server, but it can also use Apache or Lighttpd and mod_ssl to provide SSL encryption of communications. • "Failure to Restrict URL Access: Frequently an application only protects sensitive functionality by preventing the display of links or URLs to unauthorized users. Attackers can use this weakness to access and perform unauthorized operations by accessing those URLs directly." web2py maps URL requests to Python modules and functions. web2py provides a mechanism for declaring which functions are public and which require authentication and authorization. The included Role Based Access Control API allow developers to restrict access to any function based on login, group membership or group based permissions. The permissions are very granular and can be combined with database filters to allow, for example, to give access to specific tables and/or records. web2py also allows digitally signed URL and provides API to digitally sign Ajax callbacks. web2py was reviewed for security and you can find the result of the review in ref. [23]. 36 1.6 web2py complete reference manual, 5th edition In the box You can download web2py from the official web site: 1 http://www.web2py.com web2py is composed of the following components: • libraries: provide core functionality of web2py and are accessible programmatically. • web server: the Rocket WSGI web server. • the admin application: used to create, design, and manage other web2py applications. admin provides a complete web-based Integrated Development Environment (IDE) for building web2py applications. It also includes other functionality, such as web-based testing and a web-based shell. • the examples application: contains documentation and interactive examples. examples is a clone of the official web2py.com web site, and includes epydoc documentation. • the welcome application: the basic scaffolding template for any other application. By default it includes a pure CSS cascading menu and user authentication (discussed in Chapter 9). web2py is distributed in source code, and in binary form for Microsoft Windows and for Mac OS X. The source code distribution can be used in any platform where Python runs and includes the above-mentioned components. To run the source code, you need Python 2.5 or 2.7 pre-installed on the system. You also need one of the supported database engines installed. For testing and light-demand applications, you can use the SQLite database, included with Python 2.7. The binary versions of web2py (for Windows and Mac OS X) include a Python 2.7 interpreter and the SQLite database. Technically, these two are not components of web2py. Including them in the binary distributions enables you to run web2py out of the box. The following image depicts the overall web2py structure: introduction 37 At the bottom we find the interpreter. Moving up we find the web server (rocket), the libraries, and the applications. Each application consists for its own MVC design (models, controllers, views, modules, languages, databases, and static files). Each application includes it own database administration code (appadmin). Every web2py instance ships with three applications: welcome (the scaffolding app), admin (the web based IDE), and examples (copy of website and examples). 1.7 About this book This book includes the following chapters, besides this introduction: • Chapter 2 is a minimalist introduction to Python. It assumes knowledge of both procedural and object-oriented programming concepts such as loops, conditions, function calls and classes, and covers basic Python syntax. It also covers examples of Python modules that are used throughout the book. If you already know Python, you may skip Chapter 2. • Chapter 3 shows how to start web2py, discusses the administrative interface, and guides the reader through various examples of increasing complexity: an application that returns a string, a counter application, an image blog, and a full blown wiki application that allows image uploads and comments, provides authentication, authorization, web services and an RSS feed. While reading this chapter, you may need to refer to Chapter 38 web2py complete reference manual, 5th edition 2 for general Python syntax and to the following chapters for a more detailed reference about the functions that are used. • Chapter 4 covers more systematically the core structure and libraries: URL mapping, request, response, sessions, caching, schedulre, cron, internationalization and general workflow. • Chapter 5 is a reference for the template language used to build views. It shows how to embed Python code into HTML, and demonstrates the use of helpers (objects that can generate HTML). • Chapter 6 covers the Database Abstraction Layer, or DAL. The syntax of the DAL is presented through a series of examples. • Chapter 7 covers forms, form validation and form processing. FORM is the low level helper for form building. SQLFORM is the high level form builder. In Chapter 7 we also discuss Create/Read/Update/Delete (CRUD) API. • Chapter 8 covers communication with as sending emails and SMSes. • Chapter 9 covers authentication, authorization and the extensible RoleBased Access Control mechanism available in web2py. Mail configuration and CAPTCHA are also discussed here, since they are used for authentication. In the third edition of the book we have added extensive coverage of integration with third-party authentication mechanisms such as OpenID, OAuth, Google, Facebook, LinkedIn, etc. • Chapter 10 is about creating web services in web2py. We provide examples of integration with the Google Web Toolkit via Pyjamas, and with Adobe Flash via PyAMF. • Chapter 11 is about web2py and jQuery recipes. web2py is designed mainly for server-side programming, but it includes jQuery, since we have found it to be the best open-source JavaScript library available for effects and Ajax. In this chapter, we discuss how to effectively use jQuery with web2py. • Chapter 12 discusses web2py components and plugins as a way to build modular applications. We provide an example of a plugin that implements introduction 39 many commonly used functionality, such as charting, comments, and tagging. • Chapter 13 is about production deployment of web2py applications. We specifically discuss the deployment on a LAMP web server (which we consider the main deployment alternative). We discuss alterantive web servers, and configuration of the PostgreSQL database. We discuss running as a service on a Microsoft Windows environment, and deployment on some specific platforms including Google Applications Engine, Heorku, and PythonAnywhere. In this chapter, we also discuss security and scalability issues. • Chapter 14 contains a variety of other recipes to solve specific tasks, including upgrades, geocoding, pagination, the Twitter API, and more. This book only covers basic web2py functionalities and the API that ships with web2py. This book does not cover web2py appliances (i.e. ready made applications). You can download web2py appliances from the corresponding web site [24]. You can find additional topics discussed on the usergroup [25]. There is also AlterEgo [26], the old web2py blog and FAQ. This book has been written using the markmin syntax and automatically converted to HTML, LaTeX and PDF. 1.8 Support The main support channel is the usergroup [25], with dozens of posts every day. Even if you’re a newbie, don’t hesitate to ask - we’ll be pleased to help you. There is also a formal issue tracker system on http://code.google.com/p/web2py/issue. Last but not least, you can have professional support (see the web site for details). 40 1.9 web2py complete reference manual, 5th edition Contribute Any help is really appreciated. You can help other users on the user group, or by directly submitting patches on the program (at the GitHub site https://github.com/web2py/web2py). Even if you find a typo on this book, or have an improvement on it, the best way to help is by patching the book itself (which is under the source folder of the repository at https://github.com/mdipierro/web2py-book). 1.10 Elements of style PEP8 [27] contains good style practices when programming with Python. You will find that web2py does not always follow these rules. This is not because of omissions or negligence; it is our belief that the users of web2py should follow these rules and we encourage it. We chose not to follow some of those rules when defining web2py helper objects in order to minimize the probability of name conflict with objects defined by the user. For example, the class that represents ais called DIV, while according to the Python style reference it should have been called Div. We believe that, for this specific example that using an all-upper-case "DIV" is a more natural choice. Moreover, this approach leaves programmers free to create a class called "Div" if they choose to do so. Our syntax also maps naturally into the DOM notation of most browsers (including, for example, Firefox). According to the Python style guide, all-upper-case strings should be used for constants and not variables. Continuing with our example, even considering that DIV is a class, it is a special class that should never be modified by the user because doing so would break other web2py applications. Hence, we believe this qualifies the DIV class as something that should be treated as a constant, further justifying our choice of notation. In summary, the following conventions are followed: • HTML helpers and validators are all upper case for the reasons discussed above (for example DIV, A, FORM, URL). introduction 41 • The translator object T is upper case despite the fact that it is an instance of a class and not a class itself. Logically the translator object performs an action similar to the HTML helpers, it affects rendering part of the presentation. Also, T needs to be easy to locate in the code and must have a short name. • DAL classes follow the Python style guide (first letter capitalized), for example Table, Field, Query, Row, Rows, etc. In all other cases we believe we have followed, as much as possible, the Python Style Guide (PEP8). For example all instance objects are lower-case (request, response, session, cache), and all internal classes are capitalized. In all the examples of this book, web2py keywords are shown in bold, while strings and comments are shown in italic. 1.11 License web2py is licensed under the LGPL version 3 License. The full text of the license if available in ref. [28]. In accordance with LGPL you may: • redistribute web2py with your apps (including official web2py binary versions) • release your applications which use official web2py libraries under any license you wish Yet you must: • make clear in the documentation that your application uses web2py • release any modification of the web2py libraries under the LGPLv3 license The license includes the usual disclaimer: THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER 42 web2py complete reference manual, 5th edition PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. Earlier versions Earlier versions of web2py, 1.0.*-1.90.*, were released under the GPL2 license plus a commercial exception which, for practical purposes, was very similar to the current LPGLv3. Third party software distributed with web2py web2py contains third party software under the gluon/contrib/ folder and various JavaScript and CSS files. These files are distributed with web2py under their original licenses, as stated in the files. 1.12 Acknowledgments web2py was originally developed by and copyrighted by Massimo Di Pierro. The first version (1.0) was released in October, 2007. Since then it has been introduction 43 adopted by many users, some of whom have also contributed bug reports, testing, debugging, patches, and proofreading of this book. Some of the major developers and contributors are, in alphabetical order by first name: Adam Bryzak, Adam Gojdas, Adrian Klaver, Alain Boulch, Alan Etkin, Alec Taylor, Alexandre Andrade, Alexey Nezhdanov, Alvaro Justen, Anand Vaidya, Anatoly Belyakov, Ander Arbelaiz, Anders Roos, Andrew Replogle, Andrew Willimott, Angelo Compagnucci, Angelo and Villas, Annet Vermeer, Anthony Bastardi, Anton Muecki, Antonio Ramos, Arun Rajeevan, Attila Csipa, Ben Goosman, Ben Reinhart, Benjamin, Bernd Rothert, Bill Ferret, Blomqvist, Boris Manojlovic, Branko Vukelic, Brent Zeiben, Brian Cottingham, Brian Harrison, Brian Meredyk, Bruno Rocha, CJ Lazell, Caleb Hattingh, Carlos Galindo, Carlos Hanson, Carsten Haese, Cedric Meyer, Charles Law, Charles Winebrinner, Chris Clark, Chris May, Chris Sanders, Christian Foster Howes, Christopher Smiga, Christopher Steel, Clavin Sim, Cliff Kachinske, Corne Dickens, Craig Younkins, Dan McGee, Dan Ragubba, Dane Wright, Danny Morgan, Daniel Gonz, Daniel Haag, Daniel Lin, Dave Stoll, David Adley, David Harrison, David Lin, David Marko, David Wagner, Denes Lengyel, Diaz Luis, Dirk Krause, Dominic Koenig, Doug Warren, Douglas Philips, Douglas Soares de Andrade, Douglas and Alan, Dustin Bensing, Elcio Ferreira, Eric Vicenti, Erwin Olario, Falko Krause, Farsheed Ashouri, Felipe Meirelles, Flavien Scheurer, Fran Boon, Francisco Gama, Fred Yanowski, Friedrich Weber, Gabriele Alberti, Gergely Kontra, Gergely Peli, Gerley Kontra, Gilson Filho, Glenn Caltech, Graham Dumpleton, Gregory Benjamin, Gustavo Di Pietro, Gyuris Szabolcs, Hamdy Abdel-Badeea, Hans C. v. Stockhausen, Hans Donner, Hans Murx, Huaiyu Wang, Ian Reinhart Geiser, Iceberg, Igor Gassko, Ismael Serratos, Jan Beilicke, Jay Kelkar, Jeff Bauer, Jesus Matrinez, Jim Karsten, Joachim Breitsprecher, Joakim Eriksson, Joe Barnhart, Joel Carrier, Joel Samuelsson, John Heenan, Jon Romero, Jonas Rundberg, Jonathan Benn, Jonathan Lundell, Jose Jachuf, Joseph Piron, Josh Goldfoot, Josh Jaques, José Vicente de Sousa, Jurgis Pralgauskis, Keith Yang, Kenji Hosoda, Kenneth Lundstr, Kirill Spitsin, Kyle Smith, Larry Weinberg, Limodou, Loren McGinnis, Louis DaPrato, Luca De Alfaro, Luca Zachetti, Lucas D’Ávila, Madhukar R Pai, Manuele Presenti, Marc Abramowitz, 44 web2py complete reference manual, 5th edition Marcel Hellkamp, Marcel Leuthi, Marcello Della Longa, Margaret Greaney, Maria Mitica, Mariano Reingart, Marin Prajic, Marin Pranji, Marius van Niekerk, Mark Kirkwood, Mark Larsen, Mark Moore, Markus Gritsch, Mart Senecal, Martin Hufsky, Martin Mulone, Martin Weissenboeck, Mateusz Banach, Mathew Grabau, Mathieu Clabaut, Matt Doiron, Matthew Norris, Michael Fig, Michael Herman, Michael Howden, Michael Jursa, Michael Toomim, Michael Willis, Michele Comitini, Miguel Goncalves, Miguel Lopez, Mike Amy, Mike Dickun, Mike Ellis, Mike Pechkin, Milan Melena, Muhammet Aydin, Napoleon Moreno, Nathan Freeze, Niall Sweeny, Niccolo Polo, Nick Groenke, Nick Vargish, Nico de Groot, Nico Zanferrari, Nicolas Bruxer, Nik Klever, Olaf Ferger, Oliver Dain, Olivier Roch Vilato, Omi Chiba, Ondrej Such, Ont Rif, Oscar Benjamin, Osman Masood, Ovidio Marinho Falcao Neto, Pai, Panos Jee, Paolo Betti, Paolo Caruccio, Paolo Gasparello, Paolo Valleri, Patrick Breitenbach, Pearu Peterson, Peli Gergely, Pete Hunt, Peter Kirchner, Phyo Arkar Lwin, Pierre Thibault, Pieter Muller, Piotr Banasziewicz, Ramjee Ganti, Richard Gordon, Richard Ree, Robert Kooij, Robert Valentak, Roberto Perdomo, Robin Bhattacharyya, Roman Bataev, Ron McOuat, Ross Peoples, Ruijun Luo, Running Calm, Ryan Seto, Salomon Derossi, Sam Sheftel, Scott Roberts, Sergey Podlesnyi, Sharriff Aina, Simone Bizzotto, Sriram Durbha, Sterling Hankins, Stuart Rackham, Telman Yusupov, Thadeus Burgess, Thomas Dallagnese, Tim Farrell, Tim Michelsen, Tim Richardson, Timothy Farrell, Tito Garrido, Tyrone Hattingh, Vasile Ermicioi, Vidul Nikolaev Petrov, Vidul Petrov, Vinicius Assef, Vladimir Donnikov, Vladyslav Kozlovsky, Vladyslav Kozlovskyy, Wang Huaiyu, Wen Gong, Wes James, Will Stevens, Yair Eshel, Yarko Tymciurak, Yoshiyuki Nakamura, Younghyun Jo, Zahariash. I am sure I forgot somebody, so I apologize. I particularly thank Anthony, Jonathan, Mariano, Bruno, Vladyslav, Martin, Nathan, Simone, Thadeus, Tim, Iceberg, Denes, Hans, Christian, Fran and Patrick for their major contributions to web2py and Anthony, Alvaro, Brian, Bruno, Denes, Dane Denny, Erwin, Felipe, Graham, Jonathan, Hans, Kyle, Mark, Margaret, Michele, Nico, Richard, Roberto, Robin, Roman, Scott, Shane, Sharriff, Sriram, Sterling, Stuart, Thadeus, Wen (and others) for proofreading various versions of this book. Their contribution was introduction 45 invaluable. If you find any errors in this book, they are exclusively my fault, probably introduced by a last-minute edit. I also thank Ryan Steffen of Wiley Custom Learning Solutions for help with publishing the first edition of this book. web2py contains code from the following authors, whom I would like to thank: Guido van Rossum for Python [2], Peter Hunt, Richard Gordon, Timothy Farrell for the Rocket [19] web server, Christopher Dolivet for EditArea [29], Bob Ippolito for simplejson [30], Simon Cusack and Grant Edwards for pyRTF [31], Dalke Scientific Software for pyRSS2Gen [32], Mark Pilgrim for feedparser [33], Trent Mick for markdown2 [34], Allan Saddi for fcgi.py, Evan Martin for the Python memcache module [35], John Resig for jQuery [36]. I thank Helmut Epp (provost of DePaul University), David Miller (Dean of the College of Computing and Digital Media of DePaul University), and Estia Eichten (Member of MetaCryption LLC), for their continuous trust and support. Finally, I wish to thank my wife, Claudia, and my son, Marco, for putting up with me during the many hours I have spent developing web2py, exchanging emails with users and collaborators, and writing this book. This book is dedicated to them. 2 The Python language 2.1 About Python Python is a general-purpose high-level programming language. Its design philosophy emphasizes programmer productivity and code readability. It has a minimalist core syntax with very few basic commands and simple semantics, but it also has a large and comprehensive standard library, including an Application Programming Interface (API) to many of the underlying operating system (OS) functions. Python code, while minimalist, defines built-in objects such as linked lists (list), tuples (tuple), hash tables (dict), and arbitrarily long integers (long). Python supports multiple programming paradigms, including objectoriented (class), imperative (def), and functional (lambda) programming. Python has a dynamic type system and automatic memory management using reference counting (similar to Perl, Ruby, and Scheme). Python was first released by Guido van Rossum in 1991. The language has an open, community-based development model managed by the non-profit Python Software Foundation. There are many interpreters and compilers that implement the Python language, including one in Java (Jython) but, in this brief review, we refer to the reference C implementation created by Guido. You can find many tutorials, the official documentation and library references 48 web2py complete reference manual, 5th edition of the language on the official Python website. [2] For additional Python references, we can recommend the books in ref. [37] and ref. [38]. You may skip this chapter if you are already familiar with the Python language. 2.2 Starting up The binary distributions of web2py for Microsoft Windows or Apple OS X come packaged with the Python interpreter built into the distribution file itself. You can start it on Windows with the following command (type at the DOS prompt): 1 web2py.exe -S welcome On Apple OS X, enter the following command type in a Terminal window (assuming you’re in the same folder as web2py.app): 1 ./web2py.app/Contents/MacOS/web2py -S welcome On a Linux or other Unix box, chances are that you have Python already installed. If so, at a shell prompt type: 1 python web2py.py -S welcome If you do not have Python 2.5 (or later 2.x) already installed, you will have to download and install it before running web2py. The -S welcome command line option instructs web2py to run the interactive shell as if the commands were executed in a controller for the welcome application, the web2py scaffolding application. This exposes almost all web2py classes, objects and functions to you. This is the only difference between the web2py interactive command line and the normal Python command line. The admin interface also provides a web-based shell for each application. You can access the one for the "welcome" application at. 1 http://127.0.0.1:8000/admin/shell/index/welcome the python language 49 You can try all the examples in this chapter using the normal shell or the web-based shell. 2.3 help, dir The Python language provides two commands to obtain documentation about objects defined in the current scope, both built-in and user-defined. We can ask for help about an object, for example "1": 1 2 >>> help(1) Help on int object: 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class int(object) | int(x[, base]) -> integer | | Convert a string or number to an integer, if possible. A floating point | argument will be truncated towards zero (this does not include a string | representation of a floating point number!) When converting a string, use | the optional base. It is an error to supply a base when converting a | non-string. If the argument is outside the integer range a long object | will be returned instead. | | Methods defined here: | | __abs__(...) | x.__abs__() <==> abs(x) ... and, since "1" is an integer, we get a description about the int class and all its methods. Here the output has been truncated because it is very long and detailed. Similarly, we can obtain a list of methods of the object "1" with the command dir: 1 2 >>> dir(1) ['__abs__', ..., '__xor__'] 50 web2py complete reference manual, 5th edition 2.4 Types Python is a dynamically typed language, meaning that variables do not have a type and therefore do not have to be declared. Values, on the other hand, do have a type. You can query a variable for the type of value it contains: 1 2 3 4 5 6 7 8 9 >>> a = 3 >>> print type(a)>>> a = 3.14 >>> print type(a) >>> a = 'hello python' >>> print type(a) Python also includes, natively, data structures such as lists and dictionaries. 2.4.1 str Python supports the use of two different types of strings: ASCII strings and Unicode strings. ASCII strings are delimited by ’...’, "..." or by ’..’ or """...""". Triple quotes delimit multiline strings. Unicode strings start with a u followed by the string containing Unicode characters. A Unicode string can be converted into an ASCII string by choosing an encoding for example: 1 2 3 >>> a = 'this is an ASCII string' >>> b = u'This is a Unicode string' >>> a = b.encode('utf8') After executing these three commands, the resulting a is an ASCII string storing UTF8 encoded characters. By design, web2py uses UTF8 encoded strings internally. It is also possible to write variables into strings in various ways: 1 2 3 4 5 6 >>> print number is >>> print number is >>> print number is 'number is ' + str(3) 3 'number is %s' % (3) 3 'number is %(number)s' % dict(number=3) 3 The last notation is more explicit and less error prone, and is to be preferred. the python language 51 Many Python objects, for example numbers, can be serialized into strings using str or repr. These two commands are very similar but produce slightly different output. For example: 1 2 3 4 >>> for i in [3, 'hello']: print str(i), repr(i) 3 3 hello 'hello' For user-defined classes, str and repr can be defined/redefined using the special operators __str__ and __repr__. These are briefly described later on; for more, refer to the official Python documentation [39]. repr always has a default value. Another important characteristic of a Python string is that, like a list, it is an iterable object. 1 2 3 4 5 6 7 >>> for i in 'hello': print i h e l l o 2.4.2 list The main methods of a Python list are append, insert, and delete: 1 2 3 4 5 6 7 8 9 10 >>> a = [1, 2, 3] >>> print type(a) >>> a.append(8) >>> a.insert(2, 7) >>> del a[0] >>> print a [2, 7, 3, 8] >>> print len(a) 4 Lists can be sliced: 1 2 3 4 5 >>> [2, >>> [7, >>> print a[:3] 7, 3] print a[1:] 3, 8] print a[-2:] 52 6 web2py complete reference manual, 5th edition [3, 8] and concatenated: 1 2 3 4 >>> >>> >>> [2, a = [2, 3] b = [5, 6] print a + b 3, 5, 6] A list is iterable; you can loop over it: 1 2 3 4 5 6 >>> a = [1, 2, 3] >>> for i in a: print i 1 2 3 The elements of a list do not have to be of the same type; they can be any type of Python object. There is a very common situation for which a list comprehension can be used. Consider the following code: 1 2 3 4 5 6 7 >>> a = [1,2,3,4,5] >>> b = [] >>> for x in a: if x % 2 == 0: b.append(x * 3) >>> b [6, 12] This code clearly processes a list of items, selects and modifies a subset of the input list, and creates a new result list, and this code can be entirely replaced with the following list comprehension: 1 2 3 4 >>> >>> >>> [6, a = [1,2,3,4,5] b = [x * 3 for x in a if x % 2 == 0] b 12] 2.4.3 tuple A tuple is like a list, but its size and elements are immutable, while in a list they are mutable. If a tuple element is an object, the object attributes are mutable. A tuple is delimited by round brackets. 1 >>> a = (1, 2, 3) the python language 53 So while this works for a list: 1 2 3 4 >>> >>> >>> [1, a = [1, 2, 3] a[1] = 5 print a 5, 3] the element assignment does not work for a tuple: 1 2 3 4 5 6 7 >>> a = (1, 2, 3) >>> print a[1] 2 >>> a[1] = 5 Traceback (most recent call last): File " ", line 1, in TypeError: 'tuple' object does not support item assignment A tuple, like a list, is an iterable object. Notice that a tuple consisting of a single element must include a trailing comma, as shown below: 1 2 3 4 5 6 >>> a = (1) >>> print type(a) >>> a = (1,) >>> print type(a) Tuples are very useful for efficient packing of objects because of their immutability, and the brackets are often optional: 1 2 3 4 5 6 >>> a = 2, 3, 'hello' >>> x, y, z = a >>> print x 2 >>> print z hello 2.4.4 dict A Python dict-ionary is a hash table that maps a key object to a value object. For example: 1 2 3 4 5 6 7 >>> a = {'k':'v', 'k2':3} >>> a['k'] v >>> a['k2'] 3 >>> a.has_key('k') True 54 8 9 web2py complete reference manual, 5th edition >>> a.has_key('v') False Keys can be of any hashable type (int, string, or any object whose class implements the __hash__ method). Values can be of any type. Different keys and values in the same dictionary do not have to be of the same type. If the keys are alphanumeric characters, a dictionary can also be declared with the alternative syntax: 1 2 3 4 5 >>> a = dict(k='v', h2=3) >>> a['k'] v >>> print a {'k':'v', 'h2':3} Useful methods are has_key, keys, values and items: 1 2 3 4 5 6 7 >>> a = dict(k='v', k2=3) >>> print a.keys() ['k', 'k2'] >>> print a.values() ['v', 3] >>> print a.items() [('k', 'v'), ('k2', 3)] The items method produces a list of tuples, each containing a key and its associated value. Dictionary elements and list elements can be deleted with the command del: 1 2 3 4 5 6 7 8 >>> a = [1, 2, 3] >>> del a[1] >>> print a [1, 3] >>> a = dict(k='v', h2=3) >>> del a['h2'] >>> print a {'k':'v'} Internally, Python uses the hash operator to convert objects into integers, and uses that integer to determine where to store the value. 1 2 >>> hash("hello world") -1500746465 the python language 2.5 55 About indentation Python uses indentation to delimit blocks of code. A block starts with a line ending in colon, and continues for all lines that have a similar or higher indentation as the next line. For example: 1 2 3 4 5 6 7 8 >>> i = 0 >>> while i < 3: >>> print i >>> i = i + 1 >>> 0 1 2 It is common to use four spaces for each level of indentation. It is a good policy not to mix tabs with spaces, which can result in (invisible) confusion. 2.6 for...in In Python, you can loop over iterable objects: 1 2 3 4 5 6 7 >>> a = [0, 1, 'hello', 'python'] >>> for i in a: print i 0 1 hello python One common shortcut is xrange, which generates an iterable range without storing the entire list of elements. 6 >>> for i in xrange(0, 4): print i 0 1 2 3 1 for(int i=0; i<4; i=i+1) { print(i); } 1 2 3 4 5 This is equivalent to the C/C++/C#/Java syntax: Another useful command is enumerate, which counts while looping: 1 >>> a = [0, 1, 'hello', 'python'] 56 2 3 4 5 6 7 web2py complete reference manual, 5th edition >>> for i, j in enumerate(a): print i, j 0 0 1 1 2 hello 3 python There is also a keyword range(a, b, c) that returns a list of integers starting with the value a, incrementing by c, and ending with the last value smaller than b, a defaults to 0 and c defaults to 1. xrange is similar but does not actually generate the list, only an iterator over the list; thus it is better for looping. You can jump out of a loop using break 1 2 3 4 >>> for i in [1, 2, 3]: print i break 1 You can jump to the next loop iteration without executing the entire code block with continue 1 2 3 4 5 6 7 >>> for i in [1, 2, 3]: print i continue print 'test' 1 2 3 2.7 while The while loop in Python works much as it does in many other programming languages, by looping an indefinite number of times and testing a condition before each iteration. If the condition is False, the loop ends. 1 2 3 4 5 >>> i = 0 >>> while i < 10: i = i + 1 >>> print i 10 There is no loop...until construct in Python. the python language 2.8 57 if...elif...else The use of conditionals in Python is intuitive: 1 2 3 4 5 6 7 8 9 10 >>> for >>> >>> >>> >>> >>> >>> zero one other i in range(3): if i == 0: print 'zero' elif i == 1: print 'one' else: print 'other' "elif" means "else if". Both elif and else clauses are optional. There can be more than one elif but only one else statement. Complex conditions can be created using the not, and and or operators. 1 2 3 >>> for i in range(3): >>> if i == 0 or (i == 1 and i + 1 == 2): >>> print '0 or 1' 2.9 try...except...else...finally Python can throw - pardon, raise - Exceptions: 1 2 3 4 5 6 7 8 9 10 >>> try: >>> a = 1 / 0 >>> except Exception, e: >>> print 'oops: %s' % e >>> else: >>> print 'no problem here' >>> finally: >>> print 'done' oops: integer division or modulo by zero done If the exception is raised, it is caught by the except clause, which is executed, while the else clause is not. If no exception is raised, the except clause is not executed, but the else one is. The finally clause is always executed. There can be multiple except clauses for different possible exceptions: 1 2 >>> try: >>> raise SyntaxError 58 3 4 5 6 7 web2py complete reference manual, 5th edition >>> except ValueError: >>> print 'value error' >>> except SyntaxError: >>> print 'syntax error' syntax error The else and finally clauses are optional. Here is a list of built-in Python exceptions + HTTP (defined by web2py) 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 BaseException +-- HTTP (defined by web2py) +-- SystemExit +-- KeyboardInterrupt +-- Exception +-- GeneratorExit +-- StopIteration +-- StandardError | +-- ArithmeticError | | +-- FloatingPointError | | +-- OverflowError | | +-- ZeroDivisionError | +-- AssertionError | +-- AttributeError | +-- EnvironmentError | | +-- IOError | | +-- OSError | | +-- WindowsError (Windows) | | +-- VMSError (VMS) | +-- EOFError | +-- ImportError | +-- LookupError | | +-- IndexError | | +-- KeyError | +-- MemoryError | +-- NameError | | +-- UnboundLocalError | +-- ReferenceError | +-- RuntimeError | | +-- NotImplementedError | +-- SyntaxError | | +-- IndentationError | | +-- TabError | +-- SystemError | +-- TypeError | +-- ValueError | | +-- UnicodeError | | +-- UnicodeDecodeError | | +-- UnicodeEncodeError the python language 59 | | +-- UnicodeTranslateError +-- Warning +-- DeprecationWarning +-- PendingDeprecationWarning +-- RuntimeWarning +-- SyntaxWarning +-- UserWarning +-- FutureWarning +-- ImportWarning +-- UnicodeWarning 40 41 42 43 44 45 46 47 48 49 For a detailed description of each of them, refer to the official Python documentation. web2py exposes only one new exception, called HTTP. When raised, it causes the program to return an HTTP error page (for more on this refer to Chapter 4). Any object can be raised as an exception, but it is good practice to raise objects that extend one of the built-in exception classes. 2.10 def...return Functions are declared using def. Here is a typical Python function: 1 2 3 4 >>> def f(a, b): return a + b >>> print f(4, 2) 6 There is no need (or way) to specify types of the arguments or the return type(s). In this example, a function f is defined that can take two arguments. Functions are the first code syntax feature described in this chapter to introduce the concept of scope, or namespace. In the above example, the identifiers a and b are undefined outside of the scope of function f: 1 2 3 4 5 6 7 8 9 >>> def f(a): return a + 1 >>> print f(1) 2 >>> print a Traceback (most recent call last): File " ", line 1, in print a NameError: name 'a' is not defined 60 web2py complete reference manual, 5th edition Identifiers defined outside of function scope are accessible within the function; observe how the identifier a is handled in the following code: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 >>> a = 1 >>> def f(b): return a + b >>> print f(1) 2 >>> a = 2 >>> print f(1) # new value of a is used 3 >>> a = 1 # reset a >>> def g(b): a = 2 # creates a new local a return a + b >>> print g(2) 4 >>> print a # global a is unchanged 1 If a is modified, subsequent function calls will use the new value of the global a because the function definition binds the storage location of the identifier a, not the value of a itself at the time of function declaration; however, if a is assigned-to inside function g, the global a is unaffected because the new local a hides the global value. The external-scope reference can be used in the creation of closures: 1 2 3 4 5 6 7 8 9 10 11 12 13 >>> def f(x): def g(y): return x * y return g >>> doubler = f(2) # doubler is a new function >>> tripler = f(3) # tripler is a new function >>> quadrupler = f(4) # quadrupler is a new function >>> print doubler(5) 10 >>> print tripler(5) 15 >>> print quadrupler(5) 20 Function f creates new functions; and note that the scope of the name entirely internal to f. Closures are extremely powerful. g is Function arguments can have default values, and can return multiple results: 1 2 >>> def f(a, b=2): return a + b, a - b the python language 3 4 5 6 7 61 >>> x, y = f(5) >>> print x 7 >>> print y 3 Function arguments can be passed explicitly by name, and this means that the order of arguments specified in the caller can be different than the order of arguments with which the function was defined: 1 2 3 4 5 6 7 >>> def f(a, b=2): return a + b, a - b >>> x, y = f(b=5, a=2) >>> print x 7 >>> print y -3 Functions can also take a runtime-variable number of arguments: 1 2 3 4 5 6 7 >>> def f(*a, **b): return a, b >>> x, y = f(3, 'hello', c=4, test='world') >>> print x (3, 'hello') >>> print y {'c':4, 'test':'world'} Here arguments not passed by name (3, ’hello’) are stored in the tuple a, and arguments passed by name (c and test) are stored in the dictionary b. In the opposite case, a list or tuple can be passed to a function that requires individual positional arguments by unpacking them: 1 2 3 4 5 >>> def f(a, b): return a + b >>> c = (1, 2) >>> print f(*c) 3 and a dictionary can be unpacked to deliver keyword arguments: 1 2 3 4 5 >>> def f(a, b): return a + b >>> c = {'a':1, 'b':2} >>> print f(**c) 3 62 web2py complete reference manual, 5th edition 2.10.1 lambda 1 2 3 lambda provides a way to create a very short unnamed function very easily: >>> a = lambda b: b + 2 >>> print a(3) 5 The expression "lambda [a]:[b]" literally reads as "a function with arguments [a] that returns [b]". The lambda expression is itself unnamed, but the function acquires a name by being assigned to identifier a. The scoping rules for def apply to lambda equally, and in fact the code above, with respect to a, is identical to the function declaration using def: 1 2 3 4 >>> def a(b): return b + 2 >>> print a(3) 5 The only benefit of lambda is brevity; however, brevity can be very convenient in certain situations. Consider a function called map that applies a function to all items in a list, creating a new list: 1 2 3 >>> a = [1, 7, 2, 5, 4, 8] >>> map(lambda x: x + 2, a) [3, 9, 4, 7, 6, 10] This code would have doubled in size had def been used instead of lambda. The main drawback of lambda is that (in the Python implementation) the syntax allows only for a single expression; however, for longer functions, def can be used and the extra cost of providing a function name decreases as the length of the function grows. Just like def, lambda can be used to curry functions: new functions can be created by wrapping existing functions such that the new function carries a different set of arguments: 1 2 3 4 >>> def f(a, b): return a + b >>> g = lambda a: f(a, 3) >>> g(2) 5 There are many situations where currying is useful, but one of those is directly useful in web2py: caching. Suppose you have an expensive function that checks whether its argument is prime: 1 2 def isprime(number): for p in range(2, number): the python language 3 4 5 63 if (number % p) == 0: return False return True This function is obviously time consuming. Suppose you have a caching function cache.ram that takes three arguments: a key, a function and a number of seconds. 1 value = cache.ram('key', f, 60) The first time it is called, it calls the function f(), stores the output in a dictionary in memory (let’s say "d"), and returns it so that value is: 1 value = d['key']=f() The second time it is called, if the key is in the dictionary and not older than the number of seconds specified (60), it returns the corresponding value without performing the function call. 1 value = d['key'] How would you cache the output of the function isprime for any input? Here is how: 1 2 3 4 5 6 >>> number = 7 >>> seconds = 60 >>> print cache.ram(str(number), lambda: isprime(number), seconds) True >>> print cache.ram(str(number), lambda: isprime(number), seconds) True The output is always the same, but the first time is called; the second time it is not. cache.ram is called, isprime Python functions, created with either def or lambda allow re-factoring existing functions in terms of a different set of arguments. cache.ram and cache.disk are web2py caching functions. 2.11 class Because Python is dynamically typed, Python classes and objects may seem odd. In fact, you do not need to define the member variables (attributes) when declaring a class, and different instances of the same class can have different attributes. Attributes are generally associated with the instance, not 64 web2py complete reference manual, 5th edition the class (except when declared as "class attributes", which is the same as "static member variables" in C++/Java). Here is an example: 1 2 3 4 5 >>> >>> >>> >>> 3 class MyClass(object): pass myinstance = MyClass() myinstance.myvariable = 3 print myinstance.myvariable Notice that pass is a do-nothing command. In this case it is used to define a class MyClass that contains nothing. MyClass() calls the constructor of the class (in this case the default constructor) and returns an object, an instance of the class. The (object) in the class definition indicates that our class extends the built-in object class. This is not required, but it is good practice. Here is a more complex class: 1 2 3 4 5 6 7 8 9 10 >>> class MyClass(object): >>> z = 2 >>> def __init__(self, a, b): >>> self.x = a >>> self.y = b >>> def add(self): >>> return self.x + self.y + self.z >>> myinstance = MyClass(3, 4) >>> print myinstance.add() 9 Functions declared inside the class are methods. Some methods have special reserved names. For example, __init__ is the constructor. All variables are local variables of the method except variables declared outside methods. For example, z is a class variable, equivalent to a C++ static member variable that holds the same value for all instances of the class. Notice that __init__ takes 3 arguments and add takes one, and yet we call them with 2 and 0 arguments respectively. The first argument represents, by convention, the local name used inside the method to refer to the current object. Here we use self to refer to the current object, but we could have used any other name. self plays the same role as *this in C++ or this in Java, but self is not a reserved keyword. This syntax is necessary to avoid ambiguity when declaring nested classes, the python language 65 such as a class that is local to a method inside another class. 2.12 Special attributes, methods and operators Class attributes, methods, and operators starting with a double underscore are usually intended to be private (i.e. to be used internally but not exposed outside the class) although this is a convention that is not enforced by the interpreter. Some of them are reserved keywords and have a special meaning. Here, as an example, are three of them: • __len__ • __getitem__ • __setitem__ They can be used, for example, to create a container object that acts like a list: 1 2 3 4 5 6 7 8 9 10 11 >>> >>> >>> >>> >>> >>> >>> 4 >>> >>> [3, class MyList(object): def __init__(self, *a): self.a = list(a) def __len__(self): return len(self.a) def __getitem__(self, i): return self.a[i] def __setitem__(self, i, j): self.a[i] = j b = MyList(3, 4, 5) print b[1] b.a[1] = 7 print b.a 7, 5] Other special operators include __getattr__ and __setattr__, which define the get and set attributes for the class, and __sum__ and __sub__, which overload arithmetic operators. For the use of these operators we refer the reader to more advanced books on this topic. We have already mentioned the special operators __str__ and __repr__. 66 web2py complete reference manual, 5th edition 2.13 File input/output In Python you can open and write in a file with: 1 2 3 >>> file = open('myfile.txt', 'w') >>> file.write('hello world') >>> file.close() Similarly, you can read back from the file with: 1 2 3 >>> file = open('myfile.txt', 'r') >>> print file.read() hello world Alternatively, you can read in binary mode with "rb", write in binary mode with "wb", and open the file in append mode "a", using standard C notation. The read command takes an optional argument, which is the number of bytes. You can also jump to any location in a file using seek. You can read back from the file with read 1 2 3 >>> print file.seek(6) >>> print file.read() world and you can close the file with: 1 >>> file.close() In the standard distribution of Python, which is known as CPython, variables are reference-counted, including those holding file handles, so CPython knows that when the reference count of an open file handle decreases to zero, the file may be closed and the variable disposed. However, in other implementations of Python such as PyPy, garbage collection is used instead of reference counting, and this means that it is possible that there may accumulate too many open file handles at one time, resulting in an error before the gc has a chance to close and dispose of them all. Therefore it is best to explicitly close file handles when they are no longer needed. web2py provides two helper functions, read_file() and write_file() inside the gluon.fileutils namespace that encapsulate the file access and ensure that the file handles being used are properly closed. When using web2py, you do not know where the current directory is, because it depends on how web2py is configured. The variable the python language 67 contains the path to the current application. Paths can be concatenated with the command os.path.join, discussed below. request.folder 2.14 exec, eval Unlike Java, Python is a truly interpreted language. This means it has the ability to execute Python statements stored in strings. For example: 1 2 3 >>> a = "print 'hello world'" >>> exec(a) 'hello world' What just happened? The function exec tells the interpreter to call itself and execute the content of the string passed as argument. It is also possible to execute the content of a string within a context defined by the symbols in a dictionary: 1 2 3 4 >>> a = "print b" >>> c = dict(b=3) >>> exec(a, {}, c) 3 Here the interpreter, when executing the string a, sees the symbols defined in c (b in the example), but does not see c or a themselves. This is different than a restricted environment, since exec does not limit what the inner code can do; it just defines the set of variables visible to the code. A related function is eval, which works very much like exec except that it expects the argument to evaluate to a value, and it returns that value. 1 2 3 4 >>> a = "3*4" >>> b = eval(a) >>> print b 12 2.15 import The real power of Python is in its library modules. They provide a large and consistent set of Application Programming Interfaces (APIs) to many system libraries (often in a way independent of the operating system). 68 web2py complete reference manual, 5th edition For example, if you need to use a random number generator, you can do: 1 2 3 >>> import random >>> print random.randint(0, 9) 5 This prints a random integer between 0 and 9 (including 9), 5 in the example. The function randint is defined in the module random. It is also possible to import an object from a module into the current namespace: 1 2 >>> from random import randint >>> print randint(0, 9) or import all objects from a module into the current namespace: 1 2 >>> from random import * >>> print randint(0, 9) or import everything in a newly defined namespace: 1 2 >>> import random as myrand >>> print myrand.randint(0, 9) In the rest of this book, we will mainly use objects defined in modules and cPickle. os, sys, datetime, time All of the web2py objects are accessible via a module called gluon, and that is the subject of later chapters. Internally, web2py uses many Python modules (for example thread), but you rarely need to access them directly. In the following subsections we consider those modules that are most useful. 2.15.1 os This module provides an interface to the operating system API. For example: 1 2 3 >>> import os >>> os.chdir('..') >>> os.unlink('filename_to_be_deleted') Some of the os functions, such as chdir, MUST NOT be used in web2py because they are not thread-safe. is very useful; it allows the concatenation of paths in an OSindependent way: os.path.join 1 >>> import os the python language 2 3 4 69 >>> a = os.path.join('path', 'sub_path') >>> print a path/sub_path System environment variables can be accessed via: 1 >>> print os.environ which is a read-only dictionary. 2.15.2 sys The sys module contains many variables and functions, but the one we use the most is sys.path. It contains a list of paths where Python searches for modules. When we try to import a module, Python looks for it in all the folders listed in sys.path. If you install additional modules in some location and want Python to find them, you need to append the path to that location to sys.path. 1 2 >>> import sys >>> sys.path.append('path/to/my/modules') When running web2py, Python stays resident in memory, and there is only one sys.path, while there are many threads servicing the HTTP requests. To avoid a memory leak, it is best to check if a path is already present before appending: 1 2 3 >>> path = 'path/to/my/modules' >>> if not path in sys.path: sys.path.append(path) 2.15.3 datetime The use of the datetime module is best illustrated by some examples: 1 2 3 4 5 >>> import datetime >>> print datetime.datetime.today() 2008-07-04 14:03:90 >>> print datetime.date.today() 2008-07-04 Occasionally you may need to time-stamp data based on the UTC time as opposed to local time. In this case you can use the following function: 70 1 2 3 web2py complete reference manual, 5th edition >>> import datetime >>> print datetime.datetime.utcnow() 2008-07-04 14:03:90 The datetime module contains various classes: date, datetime, time and timedelta. The difference between two date or two datetime or two time objects is a timedelta: 1 2 3 4 5 >>> >>> >>> >>> 1 a = datetime.datetime(2008, 1, 1, 20, 30) b = datetime.datetime(2008, 1, 2, 20, 30) c = b - a print c.days In web2py, date and datetime are used to store the corresponding SQL types when passed to or returned from the database. 2.15.4 time The time module differs from date and datetime because it represents time as seconds from the epoch (beginning of 1970). 1 2 3 >>> import time >>> t = time.time() 1215138737.571 Refer to the Python documentation for conversion functions between time in seconds and time as a datetime. 2.15.5 cPickle This is a very powerful module. It provides functions that can serialize almost any Python object, including self-referential objects. For example, let’s build a weird object: 1 2 3 4 >>> >>> >>> >>> class MyClass(object): pass myinstance = MyClass() myinstance.x = 'something' a = [1 ,2, {'hello':'world'}, [3, 4, [myinstance]]] and now: 1 2 3 >>> import cPickle >>> b = cPickle.dumps(a) >>> c = cPickle.loads(b) the python language 71 In this example, b is a string representation of a, and c is a copy of a generated by de-serializing b. cPickle can also serialize to and de-serialize from a file: 1 2 >>> cPickle.dump(a, open('myfile.pickle', 'wb')) >>> c = cPickle.load(open('myfile.pickle', 'rb')) 3 Overview 3.1 Startup web2py comes in binary packages for Windows and Mac OS X. They include the Python interpreter so you do not need to have it pre-installed. There is also a source code version that runs on Windows, Mac, Linux, and other Unix systems. The source code package assumes that Python is already installed on the computer. web2py requires no installation. To get started, unzip the downloaded zip file for your specific operating system and execute the corresponding web2py file. On Unix and Linux (source distribution), run: 1 python web2py.py On OS X (binary distribution), run: 1 open web2py.app On Windows (binary web2py distribution), run: 1 web2py.exe On Windows (source web2py distribution), run: 1 c:/Python27/python.exe web2py.exe Attention, to run web2py on Windows from source you must install first Mark Hammond’s win32 extensions from http://sourceforge.net/projects/pywin32/. 74 web2py complete reference manual, 5th edition The web2py program accepts various command line options which are discussed later. By default, at startup, web2py displays a startup window and then displays a GUI widget that asks you to choose a one-time administrator password, the IP address of the network interface to be used for the web server, and a port number from which to serve requests. By default, web2py runs its web server on 127.0.0.1:8000 (port 8000 on localhost), but you can run it on any available IP address and port. You can query the IP address of your network interface by opening a command line and typing ipconfig on Windows or ifconfig on OS X and Linux. From now on we assume web2py is running on localhost (127.0.0.1:8000). Use 0.0.0.0:80 to run web2py publicly on any of your network interfaces. If you do not provide an administrator password, the administration interface is disabled. This is a security measure to prevent publicly exposing the admin interface. The administrative interface, admin, is only accessible from localhost unless you run web2py behind Apache with mod_proxy. If admin detects a proxy, the session cookie is set to secure and admin login does not work unless the communication between the client and the proxy goes over HTTPS; this is a security measure. All communications between the client and admin must always be local or encrypted; otherwise an attacker would be able to perform a man-in-the middle attack or a replay attack and execute arbitrary code on the server. overview 75 After the administration password has been set, web2py starts up the web browser at the page: 1 http://127.0.0.1:8000/ If the computer does not have a default browser, open a web browser and enter the URL. Clicking on "administrative interface" takes you to the login page for the administration interface. The administrator password is the password you chose at startup. Notice that there is only one administrator, and therefore only one administrator password. For security reasons, the developer is asked to choose a new password every time web2py starts unless the option is specified. This is distinct from the authentication mechanism in web2py applications. 76 web2py complete reference manual, 5th edition After the administrator logs into web2py, the browser is redirected to the "site" page. This page lists all installed web2py applications and allows the administrator to manage them. web2py comes with three applications: • An admin application, the one you are using right now. • An examples application, with the online interactive documentation and a replica of the web2py official website. • A welcome application. This is the basic template for any other web2py application. It is referred to as the scaffolding application. This is also the application that welcomes a user at startup. Ready-to-use web2py applications are referred to as web2py appliances. You can download many freely available appliances from [24]. web2py users are encouraged to submit new appliances, either in open-source or closed-source (compiled and packed) form. From the admin application’s site page, you can perform the following operations: • install an application by completing the form on the bottom right of the page. Give a name to the application, select the file containing a packaged application or the URL where the application is located, and click "submit". • uninstall an application by clicking the corresponding button. There is a confirmation page. • create a new application by choosing a name and clicking "create". overview 77 • package an application for distribution by clicking on the corresponding button. A downloaded application is a tar file containing everything, including the database. You should not untar this file; it is automatically unpackaged by web2py when installed with admin. • clean up an application’s temporary files, such as sessions, errors and cache files. • enable/disable each application. When an application is disabled it cannot be called remotely but it is not disabled form localhost. This means disabled applications can still be accessed behind a proxy. An application is disabled by creating a file called "DISABLED" in the application folder. Users who try to access a disabled application will receive a 503 HTTP error. You can use routes_onerror to customize the error page. • EDIT an application. When you create a new application using admin, it starts as a clone of the "welcome" scaffolding app with a "models/db.py" that creates a SQLite database, connects to it, instantiates Auth, Crud, and Service, and configures them. It also provides a "controller/default.py" which exposes actions "index", "download", "user" for user management, and "call" for services. In the following, we assume that these files have been removed; we will be creating apps from scratch. web2py also comes with a wizard, described later in this chapter, that can write an alternate scaffolding code for you based on layouts and plugins available on the web and based on high level description of the models. 3.2 3.2.1 Simple examples Say hello Here, as an example, we create a simple web app that displays the message "Hello from MyApp" to the user. We will call this application "myapp". We will also add a counter that counts how many times the same user visits the 78 web2py complete reference manual, 5th edition page. You can create a new application simply by typing its name in the form on the top right of the site page in admin. After you press [create], the application is created as a copy of the built-in welcome application. To run the new application, visit: 1 http://127.0.0.1:8000/myapp Now you have a copy of the welcome application. To edit an application, click on the edit button for the newly created application. The edit page tells you what is inside the application. Every web2py application consists of certain files, most of which fall into one of six categories: • models: describe the data representation. • controllers: describe the application logic and workflow. overview 79 • views: describe the data presentation. • languages: describe how to translate the application presentation to other languages. • modules: Python modules that belong to the application. • static files: static images, CSS files [40, 41, 42], JavaScript files [43, 44], etc. • plugins: groups of files designed to work together. Everything is neatly organized following the Model-View-Controller design pattern. Each section in the edit page corresponds to a subfolder in the application folder. Notice that clicking on section headings will toggle their content. Folder names under static files are also collapsible. Each file listed in the section corresponds to a file physically located in the subfolder. Any operation performed on a file via the admin interface (create, edit, delete) can be performed directly from the shell using your favorite editor. The application contains other types of files (database, session files, error files, etc.), but they are not listed on the edit page because they are not created or modified by the administrator; they are created and modified by the application itself. The controllers contain the logic and workflow of the application. Every URL gets mapped into a call to one of the functions in the controllers (actions). There are two default controllers: "appadmin.py" and "default.py". appadmin provides the database administrative interface; we do not need it now. "default.py" is the controller that you need to edit, the one that is called by default when no controller is specified in the URL. Edit the "index" function as follows: 1 2 def index(): return "Hello from MyApp" Here is what the online editor looks like: 80 web2py complete reference manual, 5th edition Save it and go back to the edit page. Click on the index link to visit the newly created page. When you visit the URL 1 http://127.0.0.1:8000/myapp/default/index the index action in the default controller of the myapp application is called. It returns a string that the browser displays for us. It should look like this: Now, edit the "index" function as follows: 1 2 def index(): return dict(message="Hello from MyApp") Also from the edit page, edit the view "default/index.html" (the view file associated with the action) and completely replace the existing contents of that file with the following: 1 2 3 4 {{=message}}
overview 5 6 81 Now the action returns a dictionary defining a message. When an action returns a dictionary, web2py looks for a view with the name 1 [controller]/[function].[extension] and executes it. Here [extension] is the requested extension. If no extension is specified, it defaults to "html", and that is what we will assume here. Under this assumption, the view is an HTML file that embeds Python code using special {{ }} tags. In particular, in the example, the {{=message}} instructs web2py to replace the tagged code with the value of the message returned by the action. Notice that message here is not a web2py keyword but is defined in the action. So far we have not used any web2py keywords. If web2py does not find the requested view, it uses the "generic.html" view that comes with every application. If an extension other than "html" is specified ("json" for example), and the view file "[controller]/[function].json" is not found, web2py looks for the view "generic.json". web2py comes with generic.html, generic.json, generic.jsonp, generic.xml, generic.rss, generic.ics (for Mac Mail Calendar), generic.map (for embedding Google Maps), and generic.pdf (based on fpdf). These generic views can be modified for each application individually, and additional views can be added easily. Generic views are a development tool. In production every action should have its own view. In fact, by default, generic views are only enabled from localhost. You can also specify a view with response.view = ’default/something.html’ Read more on this topic in Chapter 10. If you go back to "EDIT" and click on index, you will now see the following HTML page: 82 web2py complete reference manual, 5th edition 3.2.2 Debugging toolbar For debugging purposes you can insert 1 {{=response.toolbar()}} to the code in a view and it will show you some useful information, including the request, response and session objects, and list all db queries with their timing. 3.2.3 Let’s count Let’s now add a counter to this page that will count how many times the same visitor displays the page. web2py automatically and transparently tracks visitors using sessions and cookies. For each new visitor, it creates a session and assigns a unique "session_id". The session is a container for variables that are stored server-side. The unique id is sent to the browser via a cookie. When the visitor requests another page from the same application, the browser sends the cookie back, it is retrieved by web2py, and the corresponding session is restored. To use the session, modify the default controller: 1 2 3 4 5 6 def index(): if not session.counter: session.counter = 1 else: session.counter += 1 return dict(message="Hello from MyApp", counter=session.counter) Notice that counter is not a web2py keyword but session is. We are asking web2py to check whether there is a counter variable in the session and, if not, overview 83 to create one and set it to 1. If the counter is there, we ask web2py to increase the counter by 1. Finally we pass the value of the counter to the view. A more compact way to code the same function is this: 1 2 3 def index(): session.counter = (session.counter or 0) + 1 return dict(message="Hello from MyApp", counter=session.counter) Now modify the view to add a line that displays the value of the counter: 1 2 3 4 5 6 7{{=message}}
Number of visits: {{=counter}}
When you visit the index page again (and again) you should get the following HTML page: The counter is associated with each visitor, and is incremented each time the visitor reloads the page. Different visitors see different counters. 3.2.4 Say my name Now create two pages (first and second), where the first page creates a form, asks the visitor’s name, and redirects to the second page, which greets the visitor by name. 84 web2py complete reference manual, 5th edition Write the corresponding actions in the default controller: 1 2 def first(): return dict() 3 4 5 def second(): return dict() Then create a view "default/first.html" for the first action, and enter: 1 2 3 4 5 6 {{extend 'layout.html'}}What is your name?
Finally, create a view "default/second.html" for the second action: 1 2 {{extend 'layout.html'}}Hello {{=request.vars.visitor_name}}
In both views we have extended the basic "layout.html" view that comes with web2py. The layout view keeps the look and feel of the two pages consistent. The layout file can be edited and replaced easily, since it mainly contains HTML code. If you now visit the first page, type your name: and submit the form, you will receive a greeting: overview 3.2.5 85 Postbacks The mechanism for form submission that we used before is very common, but it is not good programming practice. All input should be validated and, in the above example, the burden of validation would fall on the second action. Thus the action that performs the validation is different from the action that generated the form. This tends to cause redundancy in the code. A better pattern for form submission is to submit forms to the same action that generated them, in our example the "first". The "first" action should receive the variables, process them, store them server-side, and redirect the visitor to the "second" page, which retrieves the variables. This mechanism is called a postback. Modify the default controller to implement self-submission: 1 2 def first(): if request.vars.visitor_name: 86 3 4 5 web2py complete reference manual, 5th edition session.visitor_name = request.vars.visitor_name redirect(URL('second')) return dict() 6 7 8 def second(): return dict() Then modify the "default/first.html" view: 1 2 3 4 5 6 {{extend 'layout.html'}} What is your name? and the "default/second.html" view needs to retrieve the data from the session instead of from the request.vars: 1 2 {{extend 'layout.html'}}Hello {{=session.visitor_name or "anonymous"}}
From the point of view of the visitor, the self-submission behaves exactly the same as the previous implementation. We have not added validation yet, but it is now clear that validation should be performed by the first action. This approach is better also because the name of the visitor stays in the session, and can be accessed by all actions and views in the application without having to be passed around explicitly. Note that if the "second" action is ever called before a visitor name is set, it will display "Hello anonymous" because session.visitor_name returns None. Alternatively we could have added the following code in the controller (inside the second function): 1 2 if not request.function=='first' and not session.visitor_name: redirect(URL('first')) This is an ad hoc mechanism that you can use to enforce authorization on controllers, though see Chapter 9 for a more powerful method. With web2py we can move one step further and ask web2py to generate the form for us, including validation. web2py provides helpers (FORM, INPUT, TEXTAREA, and SELECT/OPTION) with the same names as the equivalent HTML tags. They can be used to build forms either in the controller or in the view. overview 87 For example, here is one possible way to rewrite the first action: 1 2 3 4 5 6 7 def first(): form = FORM(INPUT(_name='visitor_name', requires=IS_NOT_EMPTY()), INPUT(_type='submit')) if form.process().accepted: session.visitor_name = form.vars.visitor_name redirect(URL('second')) return dict(form=form) where we are saying that the FORM tag contains two INPUT tags. The attributes of the input tags are specified by the named arguments starting with underscore. The requires argument is not a tag attribute (because it does not start by underscore) but it sets a validator for the value of visitor_name. Here is yet another better way to create the same form: 1 2 3 4 5 6 7 8 def first(): form = SQLFORM.factory(Field('visitor_name', label='what is your name?', requires=IS_NOT_EMPTY())) if form.process().accepted: session.visitor_name = form.vars.visitor_name redirect(URL('second')) return dict(form=form) The form object can be easily serialized in HTML by embedding it in the "default/first.html" view. 1 2 {{extend 'layout.html'}} {{=form}} The form.process() method applies the validators and returns the form itself. The form.accepted variable is set to True if the form was processed and passed validation. If the self-submitted form passes validation, it stores the variables in the session and redirects as before. If the form does not pass validation, error messages are inserted into the form and shown to the user, as below: 88 web2py complete reference manual, 5th edition In the next section we will show how forms can be generated automatically from a model. In all our examples we have used the session to pass the user name from the first action to the second. We could have used a different mechanism and passed data as part of a redirect URL: 1 2 3 4 5 6 def first(): form = SQLFORM.factory(Field('visitor_name', requires=IS_NOT_EMPTY())) if form.process().accepted: name = form.vars.visitor_name redirect(URL('second',vars=dict(name=name))) return dict(form=form) 7 8 9 10 def second(): name = request.vars.visitor_name or redirect(URL('first')) return dict(name=name) Mind that in general it is not a good idea to pass data from one action to another using the URL. It makes it harder to secure the application. It is safer to store the data in a session. 3.2.6 Internationalization Your code is likely to include hardcoded strings such as "What is your name?". You should be able to customize strings without editing the code and in particular insert translations for these strings in different languages. overview 89 In this way if a visitor has the language preference of the browser set to "Italian", web2py will use the Italian translation for the strings, if available. This feature of web2py is called "internationalization" and it is described in more detail in the next chapter. Here we just observe that in order to use this feature you should markup strings that needs translation. This is done by wrapping a quoted string in code such as 1 "What is your name?" with the T operator: 1 T("What is your name?") You can also mark for translations strings hardcoded in views. For example 1What is your name?
becomes 1{{=T("What is your name?")}}
It is good practice to do this for every string in the code (field labels, flash messages, etc.) except for tables and field names. Once the strings are identified and marked up, web2py takes care of almost everything else. The admin interface also provides a page where you can translate each string in the languages you desire to support. web2py includes a powerful pluralization engine which is described in the next chapter. It is integrated with both the internationalization engine and the markmin renderer. 3.3 An image blog Here, as another example, we wish to create a web application that allows the administrator to post images and give them a name, and allows the visitors of the web site to view the named images and submit comments (posts). As before, from the site page in admin, create a new application called images, and navigate to the edit page: 90 web2py complete reference manual, 5th edition We start by creating a model, a representation of the persistent data in the application (the images to upload, their names, and the comments). First, you need to create/edit a model file which, for lack of imagination, we call "db.py". We assume the code below will replace any existing code in "db.py". Models and controllers must have a .py extension since they are Python code. If the extension is not provided, it is appended by web2py. Views instead have a .html extension since they mainly contain HTML code. Edit the "db.py" file by clicking the corresponding "edit" button: and enter the following: 1 db = DAL("sqlite://storage.sqlite") 2 3 4 5 6 db.define_table('image', Field('title', unique=True), Field('file', 'upload'), format = '%(title)s') 7 8 db.define_table('post', overview 9 10 11 12 91 Field('image_id', 'reference image'), Field('author'), Field('email'), Field('body', 'text')) 13 14 15 16 17 18 db.image.title.requires = IS_NOT_IN_DB(db, db.image.title) db.post.image_id.requires = IS_IN_DB(db, db.image.id, '%(title)s') db.post.author.requires = IS_NOT_EMPTY() db.post.email.requires = IS_EMAIL() db.post.body.requires = IS_NOT_EMPTY() 19 20 db.post.image_id.writable = db.post.image_id.readable = False Let’s analyze this line by line. Line 1 defines a global variable called db that represents the database connection. In this case it is a connection to a SQLite database stored in the file "applications/images/databases/storage.sqlite". When using SQLite, if the database file does not exist, it is created. You can change the name of the file, as well as the name of the global variable db, but it is convenient to give them the same name, to make it easy to remember. Lines 3-6 define a table "image". define_table is a method of the db object. The first argument, "image", is the name of the table we are defining. The other arguments are the fields belonging to that table. This table has a field called "title", a field called "file", and a field called "id" that serves as the table primary key ("id" is not explicitly declared because all tables have an id field by default). The field "title" is a string, and the field "file" is of type "upload". "upload" is a special type of field used by the web2py Data Abstraction Layer (DAL) to store the names of uploaded files. web2py knows how to upload files (via streaming if they are large), rename them safely, and store them. When a table is defined, web2py takes one of several possible actions: • if the table does not exist, the table is created; • if the table exists and does not correspond to the definition, the table is altered accordingly, and if a field has a different type, web2py tries to convert its contents; • if the table exists and corresponds to the definition, web2py does nothing. This behavior is called "migration". In web2py migrations are automatic, but 92 web2py complete reference manual, 5th edition can be disabled for each table by passing of define_table. migrate=False as the last argument Line 6 defines a format string for the table. It determines how a record should be represented as a string. Notice that the format argument can also be a function that takes a record and returns a string. For example: 1 format=lambda row: row.title Lines 8-12 define another table called "post". A post has an "author", an "email" (we intend to store the email address of the author of the post), a "body" of type "text" (we intend to use it to store the actual comment posted by the author), and an "image_id" field of type reference that points to db.image via the "id" field. In line 14, db.image.title represents the field "title" of table "image". The attribute requires allows you to set requirements/constraints that will be enforced by web2py forms. Here we require that the "title" is unique: IS_NOT_IN_DB(db, db.image.title) Notice this is optional because it is set automatically given that unique=True). Field(’title’, The objects representing these constraints are called validators. Multiple validators can be grouped in a list. Validators are executed in the order they appear. IS_NOT_IN_DB(a, b) is a special validator that checks that the value of a field b for a new record is not already in a. Line 15 requires that the field "image_id" of table "post" is in db.image.id. As far as the database is concerned, we had already declared this when we defined the table "post". Now we are explicitly telling the model that this condition should be enforced by web2py, too, at the form processing level when a new comment is posted, so that invalid values do not propagate from input forms to the database. We also require that the "image_id" be represented by the "title", ’%(title)s’, of the corresponding record. Line 20 indicates that the field "image_id" of table "post" should not be shown in forms, writable=False and not even in read-only forms, readable=False. The meaning of the validators in lines 17-18 should be obvious. overview 93 Notice that the validator 1 db.post.image_id.requires = IS_IN_DB(db, db.image.id, '%(title)s') can be omitted (and would be automatic) if we specify a format for referenced table: 1 db.define_table('image', ..., format='%(title)s') where the format can be a string or a function that takes a record and returns a string. Once a model is defined, if there are no errors, web2py creates an application administration interface to manage the database. You access it via the "database administration" link in the edit page or directly: 1 http://127.0.0.1:8000/images/appadmin Here is a screenshot of the appadmin interface: This interface is coded in the controller called "appadmin.py" and the corresponding view "appadmin.html". From now on, we will refer to this interface simply as appadmin. It allows the administrator to insert new 94 web2py complete reference manual, 5th edition database records, edit and delete existing records, browse tables, and perform database joins. The first time appadmin is accessed, the model is executed and the tables are created. The web2py DAL translates Python code into SQL statements that are specific to the selected database back-end (SQLite in this example). You can see the generated SQL from the edit page by clicking on the "sql.log" link under "models". Notice that the link is not present until the tables have been created. If you were to edit the model and access appadmin again, web2py would generate SQL to alter the existing tables. The generated SQL is logged into "sql.log". Now go back to appadmin and try to insert a new image record: overview 95 web2py has translated the db.image.file "upload" field into an upload form for the file. When the form is submitted and an image file is uploaded, the file is renamed in a secure way that preserves the extension, it is saved with the new name under the application "uploads" folder, and the new name is stored in the db.image.file field. This process is designed to prevent directory traversal attacks. Notice that each field type is rendered by a widget. Default widgets can be overridden. When you click on a table name in appadmin, web2py performs a select of all records on the current table, identified by the DAL query 1 db.image.id > 0 and renders the result. 96 web2py complete reference manual, 5th edition You can select a different set of records by editing the DAL query and pressing [Submit]. To edit or delete a single record, click on the record id number. Because of the IS_IN_DB validator, the reference field "image_id" is rendered by a drop-down menu. The items in the drop-down are stored as keys (db.image.id), but are represented by their db.image.title, as specified by the validator. Validators are powerful objects that know how to represent fields, filter field values, generate errors, and format values extracted from the field. The following figure shows what happens when you submit a form that does not pass validation: overview 97 The same forms that are automatically generated by appadmin can also be generated programmatically via the SQLFORM helper and embedded in user applications. These forms are CSS-friendly, and can be customized. Every application has its own appadmin; therefore, appadmin itself can be modified without affecting other applications. So far, the application knows how to store data, and we have seen how to access the database via appadmin. Access to appadmin is restricted to the administrator, and it is not intended as a production web interface for the application; hence the next part of this walk-through. Specifically we want to create: • An "index" page that lists all available images sorted by title and links to detail pages for the images. 98 web2py complete reference manual, 5th edition • A "show/[id]" page that shows the visitor the requested image and allows the visitor to view and post comments. • A "download/[name]" action to download uploaded images. This is represented schematically here: Go back to the edit page and edit the "default.py" controller, replacing its contents with the following: 1 2 3 def index(): images = db().select(db.image.ALL, orderby=db.image.title) return dict(images=images) This action returns a dictionary. The keys of the items in the dictionary are interpreted as variables passed to the view associated to the action. When developing, if there is no view, the action is rendered by the "generic.html" view that is provided with every web2py application. The index action performs a select of all fields (db.image.ALL) from table image, ordered by db.image.title. The result of the select is a Rows object containing the records. Assign it to a local variable called images returned by the action to the view. images is iterable and its elements are the selected rows. For each row the columns can be accessed as dictionaries: images[0][’title’] or equivalently as images[0].title. If you do not write a view, the dictionary is rendered by "views/generic.html" and a call to the index action would look like this: overview 99 You have not created a view for this action yet, so web2py renders the set of records in plain tabular form. Proceed to create a view for the index action. Return to admin, edit "default/index.html" and replace its content with the following: 1 2 3 4 5 6 7 {{extend 'layout.html'}}Current Images
{{for image in images:}} {{=LI(A(image.title, _href=URL("show", args=image.id)))}} {{pass}}
The first thing to notice is that a view is pure HTML with special {{...}} tags. The code embedded in {{...}} is pure Python code with one caveat: indentation is irrelevant. Blocks of code start with lines ending in colon (:) and end in lines beginning with the keyword pass. In some cases the end of a block is obvious from context and the use of pass is not required. Lines 5-7 loop over the image rows and for each row image display: 1 LI(A(image.title, _href=URL('show', args=image.id)) This is a... tag that contains an ... tag which contains the image.title. The value of the hypertext reference (href attribute) is: 1 URL('show', args=image.id) 100 web2py complete reference manual, 5th edition i.e., the URL within the same application and controller as the current request that calls the function called "show", passing a single argument to the function, args=image.id. LI, A, etc. are web2py helpers that map to the corresponding HTML tags. Their unnamed arguments are interpreted as objects to be serialized and inserted in the tag’s innerHTML. Named arguments starting with an underscore (for example _href) are interpreted as tag attributes but without the underscore. For example _href is the href attribute, _class is the class attribute, etc. As an example, the following statement: 1 {{=LI(A('something', _href=URL('show', args=123))}} is rendered as: 1something A handful of helpers (INPUT, TEXTAREA, OPTION and SELECT) also support some special named attributes not starting with underscore (value, and requires). They are important for building custom forms and will be discussed later. Go back to the edit page. It now indicates that "default.py exposes index". By clicking on "index", you can visit the newly created page: 1 http://127.0.0.1:8000/images/default/index which looks like: If you click on the image name link, you are directed to: overview 1 101 http://127.0.0.1:8000/images/default/show/1 and this results in an error, since you have not yet created an action called "show" in controller "default.py". Let’s edit the "default.py" controller and replace its content with: 1 2 3 def index(): images = db().select(db.image.ALL, orderby=db.image.title) return dict(images=images) 4 5 6 7 8 9 10 11 12 def show(): image = db.image(request.args(0,cast=int)) or redirect(URL('index')) db.post.image_id.default = image.id form = SQLFORM(db.post) if form.process().accepted: response.flash = 'your comment is posted' comments = db(db.post.image_id==image.id).select() return dict(image=image, comments=comments, form=form) 13 14 15 def download(): return response.download(request, db) The controller contains two actions: "show" and "download". The "show" action selects the image with the id parsed from the request args and all comments related to the image. "show" then passes everything to the view "default/show.html". The image id referenced by: 1 URL('show', args=image.id) in "default/index.html", can be accessed as: from the "show" action. The cast=int argument is optional but very important. It attempts to cast the string value passed in the PATH_INFO into an int. On failure it raises a proper exception instead of causing a ticket. One can also specify a redirect in case of failure to cast: request.args(0,cast=int) request.args(0,cast=int,otherwise=URL(’error’)) Moreover db.image(...) is a shortcut for 1 db(db.image.id==...).select().first() The "download" action expects a filename in request.args(0), builds a path to the location where that file is supposed to be, and sends it back to the client. If the file is too large, it streams the file without incurring any memory 102 web2py complete reference manual, 5th edition overhead. Notice the following statements: • Line 7 creates an insert form SQLFORM for the the specified fields. db.post table using only • Line 8 sets the value for the reference field, which is not part of the input form because it is not in the list of fields specified above. • Line 9 processes the submitted form (the submitted form variables are in request.vars) within the current session (the session is used to prevent double submissions, and to enforce navigation). If the submitted form variables are validated, the new comment is inserted in the db.post table; otherwise the form is modified to include error messages (for example, if the author’s email address is invalid). This is all done in line 9!. • Line 10 is only executed if the form is accepted, after the record is inserted into the database table. response.flash is a web2py variable that is displayed in the views and used to notify the visitor that something happened. • Line 11 selects all comments that reference the current image. The "download" action is already defined in the "default.py" controller of the scaffolding application. The "download" action does not return a dictionary, so it does not need a view. The "show" action, though, should have a view, so return to admin and create a new view called "default/show.html". Edit this new file and replace its content with the following: 1 2 3 4 5 6 7 8 9 10 11 {{extend 'layout.html'}}Image: {{=image.title}}
{{if len(comments):}} Comments
{{for post in comments:}}
{{=post.author}} says {{=post.body}}
{{pass}} overview 12 13 14 15 16 103 {{else:}}No comments posted yet
{{pass}}Post a comment
{{=form}} This view displays the image.file by calling the "download" action inside an tag. If there are comments, it loops over them and displays each one. Here is how everything will appear to a visitor. When a visitor submits a comment via this page, the comment is stored in the database and appended at the bottom of the page. 3.3.1 Adding authentication The web2py API for Role-Based Access Control is quite sophisticated, but for now we will limit ourselves to restricting access to the show action to authenticated users, deferring a more detailed discussion to Chapter 9. To limit access to authenticated users, we need to complete three steps. In a model, for example "db.py", we need to add: 1 from gluon.tools import Auth 104 2 3 web2py complete reference manual, 5th edition auth = Auth(db) auth.define_tables(username=True) In our controller, we need to add one action: 1 2 def user(): return dict(form=auth()) This is sufficient to enable login, register, logout, etc. pages. The default layout will also show options to the corresponding pages in the top right corner. We can now decorate the functions that we want to restrict, for example: 1 2 3 @auth.requires_login() def show(): ... Any attempt to access 1 http://127.0.0.1:8000/images/default/show/[image_id] will require login. If the user is not logged it, the user will be redirected to 1 http://127.0.0.1:8000/images/default/user/login overview 105 The user function also exposes, among others, the following actions: 1 2 3 4 5 6 7 8 9 10 http://127.0.0.1:8000/images/default/user/logout http://127.0.0.1:8000/images/default/user/register http://127.0.0.1:8000/images/default/user/profile http://127.0.0.1:8000/images/default/user/change_password http://127.0.0.1:8000/images/default/user/request_reset_password http://127.0.0.1:8000/images/default/user/retrieve_username http://127.0.0.1:8000/images/default/user/retrieve_password http://127.0.0.1:8000/images/default/user/verify_email http://127.0.0.1:8000/images/default/user/impersonate http://127.0.0.1:8000/images/default/user/not_authorized Now, a first-time user needs to register in order to be able to log in and read or post comments. Both the auth object and the user function are already defined in the scaffolding application. The auth object is highly customizable and can deal with email verification, registration approvals, CAPTCHA, and alternate login methods via plugins. 3.3.2 Adding grids We can improve this further using the SQLFORM.grid and SQLFORM.smartgrid gadgets to create a management interface for our application: 1 2 3 @auth.requires_membership('manager') def manage(): grid = SQLFORM.smartgrid(db.image,linked_tables=['post']) 106 4 web2py complete reference manual, 5th edition return dict(grid=grid) with associated "views/default/manage.html" 1 2 3 {{extend 'layout.html'}}Management Interface
{{=grid}} Using appadmin create a group "manager" and make some users members of the group. They will not be able to access 1 http://127.0.0.1:8000/images/default/manage and browse, search: create, update and delete images and their comments: overview 3.3.3 107 Configuring the layout You can configure the default layout by editing "views/layout.html" but you can also configure it without editing the HTML. In fact, the "static/base.css" stylesheet is well documented and described in Chapter 5. You can change color, columns, size, borders and background without editing the HTML. If you want to edit the menu, the title or the subtitle, you can do so in any model file. The scaffolding app, sets default values of these parameters in the file "models/menu.py": 1 2 3 4 5 6 response.title = request.application response.subtitle = 'customize me!' response.meta.author = 'you' response.meta.description = 'describe your app' response.meta.keywords = 'bla bla bla' response.menu = [ [ 'Index', False, URL('index') ] ] 108 web2py complete reference manual, 5th edition 3.4 A simple wiki In this section, we build a simple wiki from scratch using only low level APIs (as opposed to using the built-in wiki capabilities of web2py demonstrated in the next section). The visitor will be able to create pages, search them (by title), and edit them. The visitor will also be able to post comments (exactly as in the previous applications), and also post documents (as attachments to the pages) and link them from the pages. As a convention, we adopt the Markmin syntax for our wiki syntax. We will also implement a search page with Ajax, an RSS feed for the pages, and a handler to search the pages via XML-RPC [45]. The following diagram lists the actions that we need to implement and the links we intend to build among them. overview 109 Start by creating a new scaffolding app, naming it "mywiki". The model must contain three tables: page, comment, and document. Both comment and document reference page because they belong to page. A document contains a file field of type upload as in the previous images application. Here is the complete model: 1 db = DAL('sqlite://storage.sqlite') 2 3 4 5 6 from gluon.tools import * auth = Auth(db) auth.define_tables() crud = Crud(db) 7 8 9 10 11 12 13 db.define_table('page', Field('title'), Field('body', 'text'), Field('created_on', 'datetime', default=request.now), Field('created_by', 'reference auth_user', default=auth.user_id), format='%(title)s') 14 15 16 17 18 19 db.define_table('post', Field('page_id', 'reference page'), Field('body', 'text'), Field('created_on', 'datetime', default=request.now), Field('created_by', 'reference auth_user', default=auth.user_id)) 20 21 22 23 24 25 26 27 db.define_table('document', Field('page_id', 'reference page'), Field('name'), Field('file', 'upload'), Field('created_on', 'datetime', default=request.now), Field('created_by', 'reference auth_user', default=auth.user_id), format='%(name)s') 28 29 30 31 32 db.page.title.requires = IS_NOT_IN_DB(db, 'page.title') db.page.body.requires = IS_NOT_EMPTY() db.page.created_by.readable = db.page.created_by.writable = False db.page.created_on.readable = db.page.created_on.writable = False 33 34 35 36 37 db.post.body.requires = IS_NOT_EMPTY() db.post.page_id.readable = db.post.page_id.writable = False db.post.created_by.readable = db.post.created_by.writable = False db.post.created_on.readable = db.post.created_on.writable = False 38 39 db.document.name.requires = IS_NOT_IN_DB(db, 'document.name') 110 40 41 42 web2py complete reference manual, 5th edition db.document.page_id.readable = db.document.page_id.writable = False db.document.created_by.readable = db.document.created_by.writable = False db.document.created_on.readable = db.document.created_on.writable = False Edit the controller "default.py" and create the following actions: • index: list all wiki pages • create: add a new wiki page • show: show a wiki page and its comments, and add new comments • edit: edit an existing page • documents: manage the documents attached to a page • download: download a document (as in the images example) • search: display a search box and, via an Ajax callback, return all matching titles as the visitor types • callback: the Ajax callback function. It returns the HTML that gets embedded in the search page while the visitor types. Here is the "default.py" controller: 1 2 3 4 5 6 7 8 def index(): """ this controller returns a dictionary rendered by the view it lists all wiki pages >>> index().has_key('pages') True """ pages = db().select(db.page.id,db.page.title,orderby=db.page.title) return dict(pages=pages) 9 10 11 12 13 14 @auth.requires_login() def create(): """creates a new empty wiki page""" form = SQLFORM(db.page).process(next=URL('index')) return dict(form=form) 15 16 17 18 19 20 21 22 23 def show(): """shows a wiki page""" this_page = db.page(request.args(0,cast=int)) or redirect(URL('index')) db.post.page_id.default = this_page.id form = SQLFORM(db.post).process() if auth.user else None pagecomments = db(db.post.page_id==this_page.id).select() return dict(page=this_page, comments=pagecomments, form=form) overview 24 25 26 27 28 29 30 111 @auth.requires_login() def edit(): """edit an existing wiki page""" this_page = db.page(request.args(0,cast=int)) or redirect(URL('index')) form = SQLFORM(db.page, this_page).process( next = URL('show',args=request.args)) return dict(form=form) 31 32 33 34 35 36 37 38 39 @auth.requires_login() def documents(): """browser, edit all documents attached to a certain page""" page = db.page(request.args(0,cast=int)) or redirect(URL('index')) db.document.page_id.default = page.id db.document.page_id.writable = False grid = SQLFORM.grid(db.document.page_id==page.id,args=[page.id]) return dict(page=page, grid=grid) 40 41 42 def user(): return dict(form=auth()) 43 44 45 46 def download(): """allows downloading of documents""" return response.download(request, db) 47 48 49 50 51 52 def search(): """an ajax wiki search page""" return dict(form=FORM(INPUT(_id='keyword',_name='keyword', _onkeyup="ajax('callback', ['keyword'], 'target');")), target_div=DIV(_id='target')) 53 54 55 56 57 58 59 def callback(): """an ajax callback that returns aof links to wiki pages""" query = db.page.title.contains(request.vars.keyword) pages = db(query).select(orderby=db.page.title) links = [A(p.title, _href=URL('show',args=p.id)) for p in pages] return UL(*links) Lines 2-6 constitute a comment for the index action. Lines 4-5 inside the comment are interpreted by python as test code (doctest). Tests can be run via the admin interface. In this case the tests verify that the index action runs without errors. Lines 18, 27, and 35 try to fetch a page record with the id in request.args(0). Lines 13, 20 define and process create forms for a new page and a new comment and 112 web2py complete reference manual, 5th edition Line 28 defines and processes an update form for a wiki page. Line 38 creates a grid object that allows to view, add and update the comments linked to a page. Some magic happens in line 51. The onkeyup attribute of the INPUT tag "keyword" is set. Every time the visitor releases a key, the JavaScript code inside the onkeyup attribute is executed, client-side. Here is the JavaScript code: 1 ajax('callback', ['keyword'], 'target'); is a JavaScript function defined in the file "web2py.js" which is included by the default "layout.html". It takes three parameters: the URL of the action that performs the synchronous callback, a list of the IDs of variables to be sent to the callback (["keyword"]), and the ID where the response has to be inserted ("target"). ajax As soon as you type something in the search box and release a key, the client calls the server and sends the content of the ’keyword’ field, and, when the sever responds, the response is embedded in the page itself as the innerHTML of the ’target’ tag. The ’target’ tag is a DIV defined in line 52. It could have been defined in the view as well. Here is the code for the view "default/create.html": 1 2 3 {{extend 'layout.html'}}
Create new wiki page
{{=form}} Assuming you are registered and logged in, if you visit the create page, you see the following: overview Here is the code for the view "default/index.html": 1 2 3 4 5 6 7 {{extend 'layout.html'}}Available wiki pages
[ {{=A('search', _href=URL('search'))}} ]
{{for page in pages:}} {{=LI(A(page.title, _href=URL('show', args=page.id)))}} {{pass}}
[ {{=A('create page', _href=URL('create'))}} ] It generates the following page: Here is the code for the view "default/show.html": 113 114 1 2 3 4 5 6 7 8 9 10 11 12 web2py complete reference manual, 5th edition {{extend 'layout.html'}}{{=page.title}}
[ {{=A('edit', _href=URL('edit', args=request.args))}} | {{=A('documents', _href=URL('documents', args=request.args))}} ]
{{=MARKMIN(page.body)}}Comments
{{for post in comments:}}{{=db.auth_user[post.created_by].first_name}} on {{=post.created_on}} says {{=post.body}}
{{pass}}Post a comment
{{=form}} If you wish to use markdown syntax instead of markmin syntax: 1 from gluon.contrib.markdown import WIKI as MARKDOWN and use MARKDOWN instead of the MARKMIN helper. Alternatively, you can choose to accept raw HTML instead of markmin syntax. In this case you would replace: 1 {{=MARKMIN(page.body)}} with: 1 {{=XML(page.body)}} (so that the XML does not get escaped, which web2py normally does by default for security reasons). This can be done better with: 1 {{=XML(page.body, sanitize=True)}} By setting sanitize=True, you tell web2py to escape unsafe XML tags such as "') Un-escaped executable input such as this (for example, entered in the body of a comment in a blog) is unsafe, because it can be used to generate Cross Site Scripting (XSS) attacks against other visitors to the page. The web2py XML helper can sanitize our text to prevent injections and escape all tags except those that you explicitly allow. Here is an example: 1 2 >>> print XML('', sanitize=True) <script>alert("unsafe!")</script> The XML constructors, by default, consider the content of some tags and some of their attributes safe. You can override the defaults using the optional permitted_tags and allowed_attributes arguments. Here are the default values of the optional arguments of the XML helper. 236 1 2 3 4 5 web2py complete reference manual, 5th edition XML(text, sanitize=False, permitted_tags=['a', 'b', 'blockquote', 'br/', 'i', 'li', 'ol', 'ul', 'p', 'cite', 'code', 'pre', 'img/'], allowed_attributes={'a':['href', 'title'], 'img':['src', 'alt'], 'blockquote':['type']}) 5.2.2 Built-in helpers A This helper is used to build links. 1 2 3 >>> print A('', XML('me'), _href='http://www.web2py.com') <click>me/b> Instead of _href you can pass the URL using the example in a view: 1 callback argument. For {{=A('click me', callback=URL('myaction'))}} and the effect of pressing the link will be an ajax call to "myaction" instead of a redirection. In this case, optionally you can specify two more arguments: target and delete: 1 2 {{=A('click me', callback=URL('myaction'), target="t")}} and the response of the ajax callback will be stored in the DIV with id equal to "t". 1{{=A('click me', callback=URL('myaction'), delete='div#b")}}and upon response, the closest tag matching "div#b" will be deleted. In this case, the button will be deleted. A typical application is: 1 {{=A('click me', callback=URL('myaction'), delete='tr")}} in a table. Pressing the button will perform the callback and delete the table row. callback and delete can be combined. The A helper takes a special argument called cid. It works as follows: 1 2 {{=A('linked page', _href='http://example.com', cid='myid')}} the views 237 and a click on the link causes the content to be loaded in the div. This is similar but more powerful than the above syntax since it is designed to refresh page components. We discuss applications of cid in more detail in Chapter 12, in the context of components. These ajax features require jQuery and "static/js/web2py_ajax.js", which are automatically included by placing {{include ’web2py_ajax.html’}} in the layout head. "views/web2py_ajax.html" defines some variables based on request and includes all necessary js and css files. B This helper makes its contents bold. 1 2 >>> print B('', XML('world'), _class='test', _id=0) <hello>world BODY This helper makes the body of a page. 1 2 >>> print BODY(' ', XML('world'), _bgcolor='red') <hello>world BR This helper creates a line break. 1 2 >>> print BR()
Notice that helpers can be repeated using the multiplication operator: 1 2 >>> print BR()*5
CAT This helper concatenates other helpers, same as TAG[”]. 1 2 >>> print CAT('Here is a ', A('link',_href=URL()), ', and here is some ', B('bold text'), '.') Here is a link, and here is some bold text. CENTER This helper centers its content. 1 2 3 >>> print CENTER('', XML('world'), _class='test', _id=0) >>> <hello>world 238 web2py complete reference manual, 5th edition CODE This helper performs syntax highlighting for Python, C, C++, HTML and web2py code, and is preferable to PRE for code listings. CODE also has the ability to create links to the web2py API documentation. Here is an example of highlighting sections of Python code. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 >>> print CODE('print "hello"', language='python').xml()Here is a similar example for HTML 1 2 3 1 2 3 >>> print CODE( >>> '{{=request.env.remote_add}}', >>> language='html')
1. print "hello"...
These are the default arguments for the CODE helper: 1 CODE("print 'hello world'", language='python', link=None, counter=1, styles={}) Supported values for the language argument are "python", "html_plain", "c", "cpp", "web2py", and "html". The "html" language interprets {{ and }} tags as "web2py" code, while "html_plain" doesn’t. If a link value is specified, for example "/examples/global/vars/", web2py API references in the code are linked to documentation at the link URL. the views 239 For example "request" would be linked to "/examples/global/vars/request". In the above example, the link URL is handled by the "vars" action in the "global.py" controller that is distributed as part of the web2py "examples" application. The counter argument is used for line numbering. It can be set to any of three different values. It can be None for no line numbers, a numerical value specifying the start number, or a string. If the counter is set to a string, it is interpreted as a prompt, and there are no line numbers. The styles argument is a bit tricky. If you look at the generated HTML above, it contains a table with two columns, and each column has its own style declared inline using CSS. The styles attributes allows you to override those two CSS styles. For example: 1 {{=CODE(...,styles={'CODE':'margin: 0;padding: 5px;border: none;'})}} The attribute must be a dictionary, and it allows two possible keys: for the style of the actual code, and LINENUMBERS for the style of the left column, which contains the line numbers. Mind that these styles completely replace the default styles and are not simply added to them. styles CODE COL 1 2 >>> print COL('a','b')... {{=request.env.remote_add}} ...
...ab COLGROUP 1 2 >>> print COLGROUP('a','b') ab DIV All helpers apart from XML are derived from DIV and inherit its basic methods. 1 2 >>> print DIV('', XML('world'), _class='test', _id=0) <hello>worldEM Emphasizes its content. 1 2 >>> print EM('', XML('world'), _class='test', _id=0) <hello>world FIELDSET This is used to create an input field together with its label. 240 1 2 web2py complete reference manual, 5th edition >>> print FIELDSET('Height:', INPUT(_name='height'), _class='test') FORM This is one of the most important helpers. In its simple form, it just makes a tag, but because helpers are objects and have knowledge of what they contain, they can process submitted forms (for example, perform validation of the fields). This will be discussed in detail in Chapter 7. 1 2 3 >>> print FORM(INPUT(_type='submit'), _action='', _method='post') The "enctype" is "multipart/form-data" by default. The constructor of a FORM, and of SQLFORM, can also take a special argument called hidden. When a dictionary is passed as hidden, its items are translated into "hidden" INPUT fields. For example: 1 2 3 >>> print FORM(hidden=dict(a='b')) H1, H2, H3, H4, H5, H6 These helpers are for paragraph headings and subheadings: 1 2 >>> print H1(' ', XML('world'), _class='test', _id=0) <hello>world
HEAD For tagging the HEAD of an HTML page. 1 2 >>> print HEAD(TITLE('', XML('world'))) <hello>world HTML This helper is a little different. In addition to making the prepends the tag with a doctype string [61, 62, 63]. 1 2 3 4 tags, it >>> print HTML(BODY('', XML('world'))) <hello>world The HTML helper also takes some additional optional arguments that have the following default: the views 1 241 HTML(..., lang='en', doctype='transitional') where doctype can be ’strict’, ’transitional’, ’frameset’, ’html5’, or a full doctype string. XHTML XHTML is similar to HTML but it creates an XHTML doctype instead. 1 XHTML(..., lang='en', doctype='transitional', xmlns='http://www.w3.org/1999/xhtml') where doctype can be ’strict’, ’transitional’, ’frameset’, or a full doctype string. HR This helper creates a horizontal line in an HTML page 1 2 >>> print HR()
I This helper makes its contents italic. 1 2 >>> print I('', XML('world'), _class='test', _id=0) <hello>world INPUT Creates an tag. An input tag may not contain other tags, and is closed by /> instead of >. The input tag has an optional attribute _type that can be set to "text" (the default), "submit", "checkbox", or "radio". 1 2 >>> print INPUT(_name='test', _value='a') It also takes an optional special argument called "value", distinct from "_value". The latter sets the default value for the input field; the former sets its current value. For an input of type "text", the former overrides the latter: 1 2 >>> print INPUT(_name='test', _value='a', value='b') For radio buttons, INPUT selectively sets the "checked" attribute: 1 2 3 4 >>> for v in ['a', 'b', 'c']: >>> print INPUT(_type='radio', _name='test', _value=v, value='b'), v a b 242 5 web2py complete reference manual, 5th edition c and similarly for checkboxes: 1 2 3 4 >>> print INPUT(_type='checkbox', _name='test', _value='a', value=True) >>> print INPUT(_type='checkbox', _name='test', _value='a', value=False) IFRAME This helper includes another web page in the current page. The url of the other page is specified via the "_src" attribute. 1 2 >>> print IFRAME(_src='http://www.web2py.com') IMG It can be used to embed images into HTML: 1 2 >>> IMG(_src='http://example.com/image.png',_alt='test') Here is a combination of A, IMG, and URL helpers for including a static image with a link: 1 2 3 4 5 >>> A(IMG(_src=URL('static','logo.png'), _alt="My Logo"), _href=URL('default','index')) LABEL It is used to create a LABEL tag for an INPUT field. 1 2 >>> print LABEL(' ', XML('world'), _class='test', _id=0) LEGEND It is used to create a legend tag for a field in a form. 1 2 >>> print LEGEND('Name', _for='myfield') LI It makes a list item and should be contained in a UL or OL tag. 1 2 >>> print LI(' ', XML('world'), _class='test', _id=0) - <hello>world
the views 243 META To be used for building META tags in the HTML head. For example: 1 2 >>> print META(_name='security', _content='high') MARKMIN Implements the markmin wiki syntax. It converts the input text into output html according to the markmin rules described in the example below: 1 2 3 >>> print MARKMIN("this is **bold** or ''italic'' and this [[a link http://web2py. com]]")this is bold or italic and this a link
The markmin syntax is described in this file that ships with web2py: 1 http://127.0.0.1:8000/examples/static/markmin.html You can use markmin to generate HTML, LaTeX and PDF documents: 1 2 3 4 5 6 7 m = "Hello **world** [[link http://web2py.com]]" from gluon.contrib.markmin.markmin2html import markmin2html print markmin2html(m) from gluon.contrib.markmin.markmin2latex import markmin2latex print markmin2latex(m) from gluon.contrib.markmin.markmin2pdf import markmin2pdf print markmin2pdf(m) # requires pdflatex (the MARKMIN helper is a shortcut for markmin2html) Here is a basic syntax primer: 244 web2py complete reference manual, 5th edition SOURCE ”italic” OUTPUT title section subsection bold italic “verbatim“ verbatim http://google.com http://google.com http://... http:... http://...png http://...mp3 http://...mp4 qr:http://... embed:http://... [[click me #myanchor]] click me Rb a sin ( x ) dx # title ## section ### subsection **bold** $$\int_ab̂ sin(x)dx$$ Simply including a link to an image, a videos or an audio files without markup result in the corresponding image, video or audio file being included automatically (for audio and video it uses html